Ich weiß, dass die 0.1
Dezimalzahl nicht exakt mit einer endlichen Binärzahl dargestellt werden kann ( Erklärung ), daher double n = 0.1
verliert sie an Genauigkeit und ist nicht genau 0.1
. Andererseits 0.5
kann genau dargestellt werden, weil es ist 0.5 = 1/2 = 0.1b
.
Trotzdem ist es verständlich, dass 0.1
dreimaliges Hinzufügen nicht genau 0.3
den folgenden Code ergibt false
:
double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
sum += d;
System.out.println(sum == 0.3); // Prints false, OK
Aber wie kommt es dann, dass 0.1
fünfmaliges Hinzufügen genau ergibt 0.5
? Der folgende Code wird gedruckt true
:
double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?
Wenn 0.1
es nicht genau dargestellt werden kann, wie kommt es dann, dass das 5-fache Hinzufügen genau 0.5
das ergibt, was genau dargestellt werden kann?
sum
es den gleichen Endwert hätte, als ob die Schleife wirklich ausgeführt worden wäre. Im C ++ - Standard wird dies als "Als-ob-Regel" oder "gleiches beobachtbares Verhalten" bezeichnet.Antworten:
Der Rundungsfehler ist nicht zufällig und versucht durch seine Implementierung, den Fehler zu minimieren. Dies bedeutet, dass der Fehler manchmal nicht sichtbar ist oder kein Fehler vorliegt.
Zum Beispiel
0.1
ist nicht genau0.1
dhnew BigDecimal("0.1") < new BigDecimal(0.1)
aber0.5
genau1.0/2
Dieses Programm zeigt Ihnen die wahren Werte.
druckt
Hinweis: Das
0.3
ist etwas falsch, aber wenn Sie zu0.4
den Bits kommen, müssen Sie eine nach unten verschieben, um in die 53-Bit-Grenze zu passen, und der Fehler wird verworfen. Wieder zurück ein Fehler einschleicht für0.6
und0.7
aber für0.8
zu1.0
den Fehlern verworfen.Der Grund für einen Fehler liegt in der eingeschränkten Genauigkeit. dh 53-Bit. Dies bedeutet, dass, da die Anzahl mehr Bits verwendet, wenn sie größer wird, Bits am Ende entfernt werden müssen. Dies führt zu Rundungen, die in diesem Fall zu Ihren Gunsten sind.
Sie können den gegenteiligen Effekt erzielen, wenn Sie eine kleinere Zahl erhalten, z. B.
0.1-0.0999
=>,1.0000000000000286E-4
und Sie sehen mehr Fehler als zuvor.Ein Beispiel hierfür ist, warum in Java 6 Math.round (0.49999999999999994) 1 zurückgibt. In diesem Fall führt der Verlust eines Bits bei der Berechnung zu einem großen Unterschied zur Antwort.
quelle
strictfp
Time verwenden würden, um Festkomma-Ganzzahlen zu berücksichtigen, denke ich. (oder BigDecimal)Der Sperrüberlauf im Gleitkomma
x + x + x
ist genau die korrekt gerundete (dh der realen 3 * am nächsten liegende) Gleitkommazahlx
,x + x + x + x
ist genau 4 *x
undx + x + x + x + x
ist wiederum die korrekt gerundete Gleitkommanäherung für 5 *x
.Das erste Ergebnis
x + x + x
ergibt sich aus der Tatsache, dassx + x
es genau ist.x + x + x
ist somit das Ergebnis nur einer Rundung.Das zweite Ergebnis ist schwieriger, eine Demonstration davon wird hier diskutiert (und Stephen Canon spielt auf einen anderen Beweis durch Fallanalyse auf den letzten 3 Ziffern von an
x
). Zusammenfassend ist entweder 3 *x
in der gleichen Binade wie 2 *x
oder es ist in der gleichen Binade wie 4 *x
, und es ist jeweils möglich zu schließen, dass der Fehler bei der dritten Addition den Fehler bei der zweiten Addition aufhebt (die erste Hinzufügung genau, wie wir bereits sagten).Das dritte Ergebnis, "
x + x + x + x + x
ist korrekt gerundet", leitet sich vom zweiten auf dieselbe Weise ab wie das erste von der Genauigkeit vonx + x
.Das zweite Ergebnis erklärt, warum
0.1 + 0.1 + 0.1 + 0.1
genau die Gleitkommazahl ist0.4
: Die rationalen Zahlen 1/10 und 4/10 werden bei der Konvertierung in Gleitkommazahl auf dieselbe Weise mit demselben relativen Fehler angenähert. Diese Gleitkommazahlen haben ein Verhältnis von genau 4 zwischen ihnen. Das erste und dritte Ergebnis zeigen, dass0.1 + 0.1 + 0.1
und es0.1 + 0.1 + 0.1 + 0.1 + 0.1
ist zu erwarten, dass sie weniger Fehler aufweisen, als durch eine naive Fehleranalyse abgeleitet werden könnte, aber an sich beziehen sie sich nur auf die Ergebnisse bzw.3 * 0.1
und5 * 0.1
, von denen erwartet werden kann, dass sie nahe beieinander liegen, aber nicht unbedingt identisch mit0.3
und0.5
.Wenn Sie
0.1
nach der vierten Addition weiter hinzufügen , werden Sie schließlich Rundungsfehler feststellen, die0.1
dazu führen, dass " n-mal zu sich selbst hinzugefügt"n * 0.1
von n / 10 abweicht und noch mehr von n / 10 abweicht. Wenn Sie die Werte von „0,1 n-mal zu sich selbst addiert“ als Funktion von n zeichnen würden, würden Sie Linien konstanter Steigung durch Binaden beobachten (sobald das Ergebnis der n-ten Addition dazu bestimmt ist, in eine bestimmte Binade zu fallen). Es ist zu erwarten, dass die Eigenschaften der Addition denen früherer Additionen ähnlich sind, die zu derselben Binade geführt haben. Innerhalb derselben Binade wächst oder schrumpft der Fehler. Wenn Sie sich die Reihenfolge der Steigungen von Binade zu Binade ansehen würden, würden Sie die sich wiederholenden Ziffern von erkennen0.1
in binär für eine Weile. Danach würde die Absorption beginnen und die Kurve würde flach werden.quelle
x + x + x
ist genau die korrekt gerundete Gleitkommazahl auf die reale 3 *x
. "Richtig gerundet" bedeutet in diesem Zusammenhang "am nächsten".Gleitkommasysteme zaubern auf verschiedene Weise, einschließlich einiger zusätzlicher Präzision beim Runden. Somit wird der sehr kleine Fehler aufgrund der ungenauen Darstellung von 0,1 auf 0,5 gerundet.
Stellen Sie sich Gleitkomma als eine großartige, aber ungenaue Möglichkeit vor, Zahlen darzustellen. Nicht alle möglichen Zahlen lassen sich in einem Computer leicht darstellen. Irrationale Zahlen wie PI. Oder wie SQRT (2). (Symbolische mathematische Systeme können sie darstellen, aber ich habe "leicht" gesagt.)
Der Gleitkommawert kann extrem nahe, aber nicht genau sein. Es kann so nah sein, dass Sie zu Pluto navigieren und um Millimeter abweichen können. Aber im mathematischen Sinne immer noch nicht genau.
Verwenden Sie kein Gleitkomma, wenn Sie genau und nicht ungefähr sein müssen. Beispielsweise möchten Buchhaltungsanwendungen eine bestimmte Anzahl von Pennys in einem Konto genau verfolgen. Ganzzahlen sind dafür gut, weil sie genau sind. Das Hauptproblem, auf das Sie bei Ganzzahlen achten müssen, ist der Überlauf.
Die Verwendung von BigDecimal für Währungen funktioniert gut, da die zugrunde liegende Darstellung eine Ganzzahl ist, wenn auch eine große.
In der Erkenntnis, dass Gleitkommazahlen ungenau sind, haben sie immer noch sehr viele Verwendungsmöglichkeiten. Koordinatensysteme für die Navigation oder Koordinaten in Grafiksystemen. Astronomische Werte. Wissenschaftliche Werte. (Sie können wahrscheinlich die genaue Masse eines Baseballs ohnehin nicht innerhalb einer Masse eines Elektrons kennen, daher spielt Ungenauigkeit keine Rolle.)
Verwenden Sie zum Zählen von Anwendungen (einschließlich Buchhaltung) eine Ganzzahl. Verwenden Sie int oder long, um die Anzahl der Personen zu zählen, die durch ein Tor gehen.
quelle
strictfp
). Nur weil Sie darauf verzichtet haben, etwas zu verstehen, heißt das nicht, dass es unergründlich ist, und dass andere darauf verzichten sollten, es zu verstehen. Unter stackoverflow.com/questions/18496560 finden Sie ein Beispiel für die Länge, in der Java-Implementierungen ausgeführt werden, um die Sprachdefinition zu implementieren (die weder Bestimmungen für Bits mit zusätzlicher Genauigkeit noch mitstrictfp
zusätzlichen Bits enthält)