Correction d'un bogue mineur dans calc.exe

Dimanche, j'étais inactif comme d'habitude, parcourant Reddit. En parcourant le plaisir des chiots et la mauvaise humeur des programmeurs, un article en particulier a attiré mon attention. C'était un bogue dans calc.exe .


Calcul de plage de dates incorrect dans la calculatrice Windows

"Eh bien, cela ressemble à une curieuse erreur, je me demande ce qui pourrait en être la cause", me suis-je dit. Le nombre de semaines, bien sûr, fait ressembler le bug à une sorte de débordement ou d'erreur de plage, vous savez, des raisons typiques. Mais il peut toujours être un peu inversé par un rayon de haute énergie provenant d'un voisin cosmique ami.

Étant intéressé par la raison, j'ai fait ce que vous faites dans de tels cas: je l'ai essayé sur ma machine pour poster "Tout fonctionne pour moi." Et la répétition de la situation depuis le poste «31 juillet - 31 décembre» sur ma machine a donné le résultat correct de «5 mois». Mais après avoir testé un peu, j'ai trouvé que "31 juillet - 30 décembre" provoque en fait une erreur. La valeur incorrecte «5 mois, 613566756 semaines, 3 jours» s'affiche.

Je n'avais pas fini de secouer le programme, puis je me suis souvenu: "Oh, la calculatrice n'est-elle pas une de ces choses pour lesquelles Microsoft a ouvert la source?" Et vraiment . Cette erreur ne pouvait pas être trop compliquée, alors j'ai pensé que j'essaierais de la trouver. Le téléchargement des sources était assez simple, et l'ajout de la charge de travail UWP requise à Visual Studio s'est également déroulé sans accroc.

La navigation dans les bases de code que vous ne connaissez pas est une chose à laquelle vous vous habituez avec le temps. Surtout lorsque vous souhaitez contribuer à des projets open source où vous trouvez le bug. Cependant, l'ignorance de XAML ou WinRT, bien sûr, ne facilite pas les choses.

J'ai ouvert le fichier de solution et regardé le projet «Calculatrice» à la recherche de n'importe quel fichier qui devrait être lié au bogue. J'ai trouvé DateCalculator.xaml , puis il semble convenir au nom DateDiff_FromDate to DateCalculatorViewModel.cpp et, enfin, DateCalculator.cpp .

Après avoir défini un point d'arrêt et en regardant certaines variables, j'ai vu que la valeur finale de DateDifference déjà incorrecte. Autrement dit, il ne s'agissait pas simplement d'une erreur de conversion en chaîne, mais d'une erreur de calcul réel.

Le calcul réel dans un pseudo-code simplifié ressemble à ceci:

 DateDifference calculate_difference(start_date, end_date) { uint[] diff_types = [year, month, week, day] uint[] typical_days_in_type = [365, 31, 7, 1] uint[] calculated_difference = [0, 0, 0, 0] date temp_pivot_date date pivot_date = start_date uint days_diff = calculate_days_difference(start_date, end_date) for(type in differenceTypes) { temp_pivot_date = pivot_date uint current_guess = days_diff /typicalDaysInType[type] if(current_guess !=0) pivot_date = advance_date_by(pivot_date, type, current_guess) int diff_remaining bool best_guess_hit = false do{ diff_remaining = calculate_days_difference(pivot_date, end_date) if(diff_remaining < 0) { // pivotDate has gone over the end date; start from the beginning of this unit current_guess = current_guess - 1 pivot_date = temp_pivot_date pivot_date = advance_date_by(pivot_date, type, current_guess) best_guess_hit = true } else if(diff_remaining > 0) { // pivot_date is still below the end date if(best_guess_hit) break; current_guess = current_guess + 1 pivot_date = advance_date_by(pivot_date, type, 1) } } while(diff_remaining!=0) temp_pivot_date = advance_date_by(temp_pivot_date, type, current_guess) pivot_date = temp_pivot_date calculated_difference[type] = current_guess days_diff = calculate_days_difference(pivot_date, end_date) } calculcated_difference[day] = days_diff return calculcated_difference } 

Ça a l'air bien. Il n'y a aucun problème de logique. Essentiellement, la fonction effectue les opérations suivantes:

  • compte les années complètes à partir de la date de début
  • à partir de la date de la dernière année complète compte les mois
  • compte les semaines à partir de la date du dernier mois complet
  • à partir de la date de la dernière semaine complète compte les jours restants

En fait, le problème est l'hypothèse que le démarrage séquentiel

 date = advance_date_by(date, month, somenumber) date = advance_date_by(date, month, 1) 

est égal à

 date = advance_date_by(date, month, somenumber + 1) 

C'est généralement la même chose. Mais la question se pose: "Si vous frappez le 31e jour du mois, le mois suivant 30 jours, vous ajoutez un mois, alors où irez-vous?"

Il semble que pour Windows.Globalization.Calendar.AddMonths (Int32), la réponse sera "le 30".

Et cela signifie que:
"31 juillet + 4 mois = 30 novembre"
"30 novembre + 1 mois = 30 décembre"
"31 juillet + 5 mois = 31 décembre"

Ainsi, l'opération AddMonths n'est ni distributive (avec AddMonth-multiplication), ni commutative , ni associative . Ce qui devrait en fait être l'opération "d'addition". N'est-ce pas amusant de travailler avec le temps et les calendriers?

Pourquoi dans ce cas, l'erreur de réglage de la plage conduit à un si grand nombre de semaines? Comme vous l'avez peut-être deviné, cela est dû au fait que days_diff est un type non signé. Cela transforme -1 jour en une énorme quantité, qui est ensuite transmise à la prochaine itération du cycle avec des semaines. Qui essaie alors de corriger la situation en diminuant current_guess mais pas en diminuant la variable non signée.

Eh bien, c'était une façon intéressante de passer le dimanche. J'ai créé une demande de pull sur Github avec un «correctif» minimal. Je mets la «correction» entre guillemets, car maintenant le calcul ressemble à ceci:



Je pense que techniquement c'est le résultat correct, si l'on suppose que "31 juillet + 4 mois = 30 novembre". Bien que cette option ne soit pas entièrement compatible avec l'intuition humaine concernant la différence de dates. Mais en tout cas, c'est moins mal qu'il ne l'était.

Source: https://habr.com/ru/post/fr456684/


All Articles