Warum liefert das Ändern der Summenreihenfolge ein anderes Ergebnis?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Sowohl Java als auch JavaScript geben die gleichen Ergebnisse zurück.
Ich verstehe, dass aufgrund der Art und Weise, wie Gleitkommazahlen binär dargestellt werden, einige rationale Zahlen ( wie 1/3 - 0,333333 ... ) nicht genau dargestellt werden können.
Warum wirkt sich eine einfache Änderung der Reihenfolge der Elemente auf das Ergebnis aus?
java
javascript
floating-point
Marlon Bernardes
quelle
quelle
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
. B. ). Daher ja: Seien Sie vorsichtig bei der Auswahl der Reihenfolge der Beträge und anderer Operationen. Einige Sprachen bieten eine integrierte Funktion zum Ausführen von "hochpräzisen" Summen (z. B. Pythonsmath.fsum
). Daher sollten Sie diese Funktionen möglicherweise anstelle des naiven Summenalgorithmus verwenden.Antworten:
Dadurch werden die Punkte, an denen die Werte gerundet werden, basierend auf ihrer Größe geändert. Nehmen wir als Beispiel für die Art der Dinge, die wir sehen, an, dass wir anstelle des binären Gleitkommas einen dezimalen Gleitkommatyp mit 4 signifikanten Stellen verwenden, bei dem jede Addition mit "unendlicher" Genauigkeit ausgeführt und dann auf gerundet wird die nächste darstellbare Zahl. Hier sind zwei Summen:
Wir brauchen nicht einmal Nicht-Ganzzahlen, damit dies ein Problem darstellt:
Dies zeigt möglicherweise deutlicher, dass der wichtige Teil darin besteht, dass wir eine begrenzte Anzahl von signifikanten Stellen haben - nicht eine begrenzte Anzahl von Dezimalstellen . Wenn wir immer die gleiche Anzahl von Dezimalstellen beibehalten könnten, wären wir zumindest mit Addition und Subtraktion in Ordnung (solange die Werte nicht überlaufen). Das Problem ist, dass bei größeren Zahlen kleinere Informationen verloren gehen - in diesem Fall wird der 10001 auf 10000 gerundet. (Dies ist ein Beispiel für das Problem, das Eric Lippert in seiner Antwort festgestellt hat .)
Es ist wichtig zu beachten, dass die Werte in der ersten Zeile der rechten Seite in allen Fällen gleich sind. Obwohl es wichtig ist zu verstehen, dass Ihre Dezimalzahlen (23,53, 5,88, 17,64) nicht genau als
double
Werte dargestellt werden, ist dies der Fall nur ein Problem wegen der oben gezeigten Probleme.quelle
May extend this later - out of time right now!
warten gespannt auf das @ Jondouble
undfloat
bei sehr großen Zahlen aufeinanderfolgende darstellbare Zahlen sind mehr als 1 voneinander entfernt.Hier ist, was in binären los ist. Wie wir wissen, können einige Gleitkommawerte nicht genau binär dargestellt werden, selbst wenn sie genau dezimal dargestellt werden können. Diese 3 Zahlen sind nur Beispiele für diese Tatsache.
Mit diesem Programm gebe ich die hexadezimalen Darstellungen jeder Zahl und die Ergebnisse jeder Addition aus.
Die
printValueAndInHex
Methode ist nur ein Hex-Drucker-Helfer.Die Ausgabe ist wie folgt:
Die ersten 4 Zahlen sind
x
,y
,z
unds
hexadezimalen Darstellungen s‘. In der IEEE-Gleitkomma-Darstellung repräsentieren die Bits 2-12 den binären Exponenten , dh die Skala der Zahl. (Das erste Bit ist das Vorzeichenbit und die verbleibenden Bits für die Mantisse .) Der dargestellte Exponent ist tatsächlich die Binärzahl minus 1023.Die Exponenten für die ersten 4 Zahlen werden extrahiert:
Erster Satz von Ergänzungen
Die zweite Zahl (
y
) ist kleiner. Wenn Sie diese beiden Zahlen addierenx + y
, werden die letzten 2 Bits der zweiten Zahl (01
) außerhalb des Bereichs verschoben und nicht in die Berechnung einbezogen.Der zweite Zusatz fügt
x + y
undz
und fügt zwei Zahlen derselben Skala hinzu.Zweiter Satz von Ergänzungen
Hier
x + z
tritt zuerst auf. Sie haben den gleichen Maßstab, aber sie ergeben eine Zahl, die höher ist:Die zweite Addition fügt
x + z
und hinzuy
, und jetzt werden 3 Bits entfernty
, um die Zahlen (101
) hinzuzufügen . Hier muss es eine Runde nach oben geben, da das Ergebnis die nächste Gleitkommazahl ist:4047866666666666
für den ersten Satz von Ergänzungen vs.4047866666666667
für den zweiten Satz von Ergänzungen. Dieser Fehler ist signifikant genug, um im Ausdruck der Gesamtsumme angezeigt zu werden.Seien Sie abschließend vorsichtig, wenn Sie mathematische Operationen an IEEE-Zahlen ausführen. Einige Darstellungen sind ungenau und werden noch ungenauer, wenn die Skalen unterschiedlich sind. Addiere und subtrahiere Zahlen ähnlichen Maßstabs, wenn du kannst.
quelle
=)
+1 für Ihren Hex-Drucker-Helfer ... das ist wirklich ordentlich!Jons Antwort ist natürlich richtig. In Ihrem Fall ist der Fehler nicht größer als der Fehler, den Sie bei einer einfachen Gleitkommaoperation akkumulieren würden. Sie haben ein Szenario, in dem in einem Fall kein Fehler und in einem anderen ein winziger Fehler auftritt. Das ist eigentlich kein so interessantes Szenario. Eine gute Frage ist: Gibt es Szenarien, in denen die Änderung der Berechnungsreihenfolge von einem winzigen Fehler zu einem (relativ) enormen Fehler führt? Die Antwort lautet eindeutig ja.
Betrachten Sie zum Beispiel:
vs.
vs.
Offensichtlich wären sie in exakter Arithmetik gleich. Es ist unterhaltsam zu versuchen, Werte für a, b, c, d, e, f, g, h zu finden, so dass sich die Werte von x1 und x2 und x3 um eine große Menge unterscheiden. Sehen Sie, ob Sie das können!
quelle
double d = double.MaxValue; Console.WriteLine(d + d - d - d); Console.WriteLine(d - d + d - d);
- Die Ausgabe ist Infinity dann 0.Dies deckt tatsächlich viel mehr als nur Java und Javascript ab und würde wahrscheinlich jede Programmiersprache beeinflussen, die Floats oder Doubles verwendet.
Im Speicher verwenden Gleitkommazahlen ein spezielles Format nach IEEE 754 (der Konverter bietet eine viel bessere Erklärung als ich).
Wie auch immer, hier ist der Schwimmerkonverter.
http://www.h-schmidt.net/FloatConverter/
Die Sache mit der Reihenfolge der Operationen ist die "Feinheit" der Operation.
Ihre erste Zeile ergibt 29,41 aus den ersten beiden Werten, was uns 2 ^ 4 als Exponenten gibt.
Ihre zweite Zeile ergibt 41,17, was uns 2 ^ 5 als Exponenten gibt.
Wir verlieren eine signifikante Zahl, indem wir den Exponenten erhöhen, was wahrscheinlich das Ergebnis verändern wird.
Wenn Sie das letzte Bit ganz rechts für 41,17 ein- und ausschalten, können Sie sehen, dass etwas so "unbedeutendes" wie 1/2 ^ 23 des Exponenten ausreicht, um diese Gleitkommadifferenz zu verursachen.
Bearbeiten: Für diejenigen unter Ihnen, die sich an bedeutende Zahlen erinnern, würde dies unter diese Kategorie fallen. 10 ^ 4 + 4999 mit einer signifikanten Zahl von 1 wird 10 ^ 4 sein. In diesem Fall ist die signifikante Zahl viel kleiner, aber wir können die Ergebnisse mit der daran angehängten .00000000004 sehen.
quelle
Gleitkommazahlen werden im IEEE 754-Format dargestellt, das eine bestimmte Bitgröße für die Mantisse (Signifikand) bereitstellt. Leider gibt Ihnen dies eine bestimmte Anzahl von 'Bruchbausteinen', mit denen Sie spielen können, und bestimmte Bruchwerte können nicht genau dargestellt werden.
Was in Ihrem Fall passiert, ist, dass im zweiten Fall die Addition aufgrund der Reihenfolge, in der die Additionen ausgewertet werden, wahrscheinlich auf ein genaues Problem stößt. Ich habe die Werte nicht berechnet, aber es könnte zum Beispiel sein, dass 23,53 + 17,64 nicht genau dargestellt werden können, während 23,53 + 5,88 dies können.
Leider ist es ein bekanntes Problem, mit dem Sie sich nur befassen müssen.
quelle
Ich glaube, das hat mit der Reihenfolge der Auswertung zu tun. Während die Summe in einer mathematischen Welt natürlich dieselbe ist, ist sie in der binären Welt anstelle von A + B + C = D gleich
Es gibt also diesen zweiten Schritt, in dem Gleitkommazahlen aussteigen können.
Wenn Sie die Reihenfolge ändern,
quelle