Problem
Fließkommazahlen können nicht alle Dezimalwerte exakt speichern. Bei der Verwendung von Fließkommaformaten kommt es daher immer zu Rundungsfehlern bei den Eingabewerten. Die Fehler bei den Eingaben führen natürlich auch zu Fehlern bei der Ausgabe. Im Falle einer diskreten Funktion oder eines diskreten Operators kann es bei der Ausgabe große Unterschiede um den Punkt herum geben, an dem die Funktion oder der Operator diskret ist.
Eingabe und Ausgabe von Fließkommazahlen
Bei der Verwendung von Fließkomma-Variablen sollten Sie sich dessen also immer bewusst sein. Und was auch immer Sie aus einer Berechnung mit Fließkommazahlen ausgeben möchten, sollte vor der Anzeige immer entsprechend formatiert/konditioniert werden.
Wenn nur kontinuierliche Funktionen und Operatoren verwendet werden, reicht es oft aus, auf die gewünschte Genauigkeit zu runden (nicht abzuschneiden). Die Standardformatierungsfunktionen für die Konvertierung von Fließkommazahlen in Zeichenketten erledigen dies normalerweise für Sie.
Da die Rundung einen Fehler hinzufügt, der dazu führen kann, dass der Gesamtfehler mehr als die Hälfte der gewünschten Genauigkeit beträgt, sollte die Ausgabe auf der Grundlage der erwarteten Genauigkeit der Eingaben und der gewünschten Genauigkeit der Ausgabe korrigiert werden. Sie sollten
- Runden Sie die Eingaben auf die erwartete Genauigkeit oder stellen Sie sicher, dass keine Werte mit höherer Genauigkeit eingegeben werden können.
- Fügen Sie den Ausgaben vor dem Runden/Formatieren einen kleinen Wert hinzu, der kleiner oder gleich 1/4 der gewünschten Genauigkeit und größer als der maximal zu erwartende Fehler durch Rundungsfehler bei der Eingabe und während der Berechnung ist. Wenn dies nicht möglich ist, reicht die Kombination der Genauigkeit des verwendeten Datentyps nicht aus, um die gewünschte Ausgabegenauigkeit für Ihre Berechnung zu erreichen.
Diese beiden Dinge werden in der Regel nicht gemacht, und in den meisten Fällen sind die Unterschiede, die dadurch entstehen, zu gering, um für die meisten Benutzer von Bedeutung zu sein, aber ich hatte bereits ein Projekt, bei dem die Ausgabe ohne diese Korrekturen von den Benutzern nicht akzeptiert wurde.
Diskrete Funktionen oder Operatoren (wie Modula)
Wenn es sich um diskrete Operatoren oder Funktionen handelt, sind möglicherweise zusätzliche Korrekturen erforderlich, um sicherzustellen, dass die Ausgabe den Erwartungen entspricht. Das Runden und Hinzufügen kleiner Korrekturen vor dem Runden kann das Problem nicht lösen.
Eine besondere Prüfung/Korrektur von Berechnungszwischenergebnissen unmittelbar nach Anwendung der diskreten Funktion oder des Operators kann erforderlich sein. Für einen speziellen Fall (Modula-Operator), siehe meine Antwort auf die Frage: Warum gibt der Modulus-Operator eine Bruchzahl in Javascript zurück?
Besser das Problem vermeiden
Es ist oft effizienter, diese Probleme zu vermeiden, indem man für solche Berechnungen Datentypen (Ganzzahl- oder Festkommaformate) verwendet, die die erwartete Eingabe ohne Rundungsfehler speichern können. Ein Beispiel dafür ist, dass Sie niemals Fließkommazahlen für Finanzberechnungen verwenden sollten.
157 Stimmen
Eigentlich liegt der Fehler daran, dass es keine Möglichkeit gibt, die
0.1
in eine endliche binäre Gleitkommazahl.19 Stimmen
Die meisten Brüche lassen sich nicht mit exakter Genauigkeit in eine Dezimalzahl umwandeln. Eine gute Erklärung finden Sie hier: docs.python.org/release/2.5.1/tut/node16.html
8 Stimmen
Mögliche Duplikate von Ist die Mathematik von JavaScript kaputt?
4 Stimmen
@AaronDigulla:
(new Number(0.1)).valueOf()
es0.1
.65 Stimmen
@SalmanA: Dass Ihre JavaScript-Laufzeitumgebung dieses Problem vor Ihnen verbirgt, bedeutet nicht, dass ich falsch liege.
12 Stimmen
Ich stimme Aaron nicht zu, es gibt Möglichkeiten, 0,1 perfekt und vollständig binär zu codieren. Aber IEEE 754 definiert dies nicht unbedingt. Stellen Sie sich eine Darstellung vor, bei der Sie einerseits den ganzzahligen Teil binär kodieren, andererseits den dezimalen Teil, bis zu n Dezimalstellen, ebenfalls binär, wie eine normale ganze Zahl > 0, und schließlich die Position des Dezimalpunkts. Nun, Sie würden 0,1 perfekt darstellen, ohne Fehler. Btw, da JS intern eine endliche Anzahl von Dezimalstellen verwendet, könnten die Entwickler genauso gut den Mut haben, diesen Fehler bei den letzten Dezimalstellen zu vermeiden.
1 Stimmen
@epascarello In dieser Frage geht es darum, warum das passiert, in dieser Frage darum, wie man es beheben kann. Die Antworten sind unterschiedlich, also sind auch die Fragen unterschiedlich.
1 Stimmen
@AaronDigulla warum JS tut dies und C# nicht? Ich habe bemerkt, dass JS dies auch bei der Summe macht, was keinen Sinn ergibt. Warum kann nicht addieren 1.12345678 + 1.12345678 richtig. Keine Multiplikation, keine Division, kein Überlauf
1 Stimmen
Ich habe zwei Gleichungen/Formeln für die Berechnung des umgekehrten Prozentsatzes geschrieben, die Sie hier finden können: stackoverflow.com/a/54125117/850840 und hier stackoverflow.com/a/54125466/850840
2 Stimmen
@PawelCioch Haben Sie mit genau denselben Zahlen getestet? Hast du das binäre Muster zum Initialisieren der Variablen verwendet (sonst bekommst du Rundungsfehler vom Float->String-Konverter)? Die meisten Sprachen verwenden die CPU für Fließkomma-Mathematik (also verhalten sie sich gleich), nur der Code zum Parsen und Formatieren der Zahlen ist anders.
1 Stimmen
So lustig und noch seltsamer mit 0.1 + 0.2 _
2 Stimmen
Binäre Brüche können nur 1/2, 1/4, 1/8, 1/16, 1/32... richtig verarbeiten. Und weil 0,3 und 0,2 keine Brüche sind, die mit den obigen Brüchen gebildet werden können, gibt es dieses "seltsame" Verhalten. Dies hier hat mir sehr geholfen: youtube.com/watch?v=PZRI1IfStY0
2 Stimmen
@FabienHaddadi (ich kommentiere das hauptsächlich, um mein Verständnis zu festigen) Ich denke, es ist eine Frage der Definition. Für Sie bedeutet "eine Zahl binär kodieren" "eine Möglichkeit zu haben, diese Zahl mit 0 und 1 darzustellen". Für Aaron bedeutet es, "sie als endliche (pseudo-)geometrische Summe mit 2 als gemeinsamem Verhältnis und Gewichten von 0 oder 1 zu schreiben".
2 Stimmen
@user56202: Ja, es muss eine Frage der Definitionen sein. Ich habe das Binärsystem auf einem alten wissenschaftlichen Taschenrechner HP48 gelernt. Früher wurde eine reelle Zahl durch zwei Komponenten dargestellt: eine vorzeichenbehaftete Mantisse und ein vorzeichenbehafteter Zehner-Exponent. Nun ist die Dezimalzahl 0,1 gleich einem Faktor von zehn hoch minus eins. Dies ist für den HP48 trivial zu speichern.