Behebung eines kleinen Fehlers in calc.exe

Am Sonntag war ich wie immer untätig und stöberte durch Reddit. Als ich durch den Welpenspaß und die schlechte Laune der Programmierer blätterte, erregte ein bestimmter Beitrag meine Aufmerksamkeit. Es war ein Fehler in calc.exe .


Falsche Datumsbereichsberechnung in Windows Calculator

"Nun, das klingt nach einem merkwürdigen Fehler, ich frage mich, was das verursachen könnte", dachte ich mir. Die Anzahl der Wochen lässt den Fehler natürlich wie einen Überlauf- oder Bereichsfehler aussehen, wissen Sie, typische Gründe. Aber es kann immer ein umgekehrtes Stück von einem energiereichen Strahl eines freundlichen kosmischen Nachbarn sein.

Da ich mich für den Grund interessierte, tat ich, was Sie in solchen Fällen taten: Ich versuchte es auf meinem Computer, um "Alles funktioniert für mich" zu posten. Und die Wiederholung der Situation von der Post "31. Juli - 31. Dezember" auf meinem Computer ergab das korrekte Ergebnis von "5 Monate". Aber nachdem ich ein bisschen getestet hatte, stellte ich fest, dass "31. Juli - 30. Dezember" tatsächlich einen Fehler verursacht. Der falsche Wert "5 Monate, 613566756 Wochen, 3 Tage" wird angezeigt.

Ich hatte das Programm noch nicht fertig geschüttelt und erinnerte mich dann: "Oh, ist der Taschenrechner nicht eines der Dinge, für die Microsoft die Quelle geöffnet hat?" Und wirklich . Dieser Fehler konnte nicht zu kompliziert sein, also dachte ich, ich würde versuchen, ihn zu finden. Das Herunterladen der Quellen war recht einfach, und das Hinzufügen der erforderlichen UWP-Workload zu Visual Studio verlief ebenfalls reibungslos.

Das Navigieren in Codebasen, mit denen Sie nicht vertraut sind, ist etwas, an das Sie sich mit der Zeit gewöhnen. Besonders wenn Sie zu Open Source-Projekten beitragen möchten, bei denen Sie den Fehler finden. Die Unkenntnis von XAML oder WinRT macht die Sache natürlich nicht einfacher.

Ich öffnete die Lösungsdatei und suchte im Projekt „Rechner“ nach Dateien, die mit dem Fehler zusammenhängen sollten. Ich habe DateCalculator.xaml gefunden, dann scheint es für den Namen DateDiff_FromDate to DateCalculatorViewModel.cpp und schließlich DateCalculator.cpp .

Nachdem ich einen Haltepunkt gesetzt und einige Variablen betrachtet hatte, stellte ich fest, dass der endgültige DateDifference Wert bereits falsch ist. Das heißt, es war nicht nur ein Konvertierungsfehler in eine Zeichenfolge, sondern ein Fehler der tatsächlichen Berechnung.

Die eigentliche Berechnung in einem vereinfachten Pseudocode sieht ungefähr so ​​aus:

 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 } 

Es sieht gut aus. Es gibt keine Probleme in der Logik. Im Wesentlichen führt die Funktion Folgendes aus:

  • zählt volle Jahre ab dem Startdatum
  • ab dem Datum des letzten vollen Jahres zählen Monate
  • zählt Wochen ab dem Datum des letzten vollen Monats
  • ab dem Datum der letzten vollen Woche zählen die verbleibenden Tage

Tatsächlich ist das Problem die Annahme, dass der sequentielle Start

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

ist gleich

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

Dies ist normalerweise das Gleiche. Es stellt sich jedoch die Frage: "Wenn Sie den 31. Tag des Monats, den nächsten Monat 30 Tage, erreichen, fügen Sie einen Monat hinzu. Wohin gehen Sie dann?"

Für Windows.Globalization.Calendar.AddMonths (Int32) scheint die Antwort "am 30." zu sein.

Und das bedeutet:
"31. Juli + 4 Monate = 30. November"
"30. November + 1 Monat = 30. Dezember"
"31. Juli + 5 Monate = 31. Dezember"

Daher ist die AddMonths-Operation weder verteilend (mit AddMonth-Multiplikation) noch kommutativ oder assoziativ . Was sollte eigentlich die Operation der "Addition" sein. Macht es nicht Spaß, mit Zeit und Kalendern zu arbeiten?

Warum führt in diesem Fall der Fehler beim Einstellen des Bereichs zu einer so großen Anzahl von Wochen? Wie Sie vielleicht vermutet haben, liegt dies an der Tatsache, dass days_diff ein Typ ohne days_diff ist. Dies macht -1 Tage zu einer riesigen Menge, die dann mit Wochen an die nächste Iteration des Zyklus weitergegeben wird. Was dann versucht, die Situation zu korrigieren, indem current_guess verringert wird, aber die vorzeichenlose Variable nicht verringert wird.

Das war eine interessante Art, den Sonntag zu verbringen. Ich habe eine Pull-Anfrage für Github mit einem minimalen "Fix" erstellt. Ich habe die "Korrektur" in Anführungszeichen gesetzt, weil die Berechnung jetzt so aussieht:



Ich denke, dass dies technisch gesehen das richtige Ergebnis ist, wenn wir davon ausgehen, dass "31. Juli + 4 Monate = 30. November". Obwohl diese Option nicht ganz mit der menschlichen Intuition über den Unterschied in den Daten übereinstimmt. Aber auf jeden Fall ist das weniger falsch als es war.

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


All Articles