Frage
Ich kann nur annehmen, dass dies ein Fehler ist. Die erste assert geht, während die zweite nicht:
double sum_1 = 4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);
double t1 = 4.0, t2 = 6.3;
double sum_2 = t1 + t2;
assert(sum_2 == t1 + t2);
Wenn kein Fehler, warum?
Lösung
Das ist etwas, was mich gebissen hat, auch.
Ja, Gleitkommazahlen sollten nie wegen Rundungsfehler auf Gleichheit verglichen werden, und Sie wussten wahrscheinlich, dass.
Aber in diesem Fall sind Sie t1+t2
Berechnung Berechnen Sie es dann erneut. Sicher , die ein identisches Ergebnis produzieren?
Hier ist, was wohl los ist. Ich wette, Sie diese auf einem x86-CPU ausgeführt wird, richtig? Die x86 FPU 80 verwendet Bits, das für seine internen Register, aber Werte im Speicher als 64-Bit-Doppel gespeichert.
So t1+t2
wird zunächst mit 80 Bit Genauigkeit berechnet wird, dann - ich nehme an - gespeichert, um Speicher in sum_2
mit 64 Bit Genauigkeit - und einige Runden stattfinden. Für die assert, dann ist es geladen zurück in eine Gleitkommaregister und t1+t2
wird erneut berechnet, wiederum mit 80 Bit Genauigkeit. So, jetzt Sie vergleichen sum_2
, die zuvor auf eine 64-Bit gerundet wurde Gleitkommawert mit t1+t2
, die mit einer höheren Genauigkeit (80 Bit) berechnet wurde -. Und deshalb die Werte nicht genau identisch
Bearbeiten Warum also der erste Testdurchlauf? In diesem Fall wertet der Compiler wahrscheinlich 4.0+6.3
bei der Kompilierung und speichern sie als 64-Bit-Größe - sowohl für die Zuordnung und für die Assertion. So identische Werte verglichen werden, und die Assertion geht.
Second Bearbeiten Hier ist der Assembler-Code für den zweiten Teil des Codes erzeugt (gcc, x86), mit Kommentaren - folgt ziemlich genau das Szenario oben skizziert:
// t1 = 4.0
fldl LC3
fstpl -16(%ebp)
// t2 = 6.3
fldl LC4
fstpl -24(%ebp)
// sum_2 = t1+t2
fldl -16(%ebp)
faddl -24(%ebp)
fstpl -32(%ebp)
// Compute t1+t2 again
fldl -16(%ebp)
faddl -24(%ebp)
// Load sum_2 from memory and compare
fldl -32(%ebp)
fxch %st(1)
fucompp
Interessante Randnotiz: Diese wurde ohne Optimierung zusammengestellt. Wenn es mit -O3
kompiliert der Compiler optimiert alle des Codes entfernt.
Andere Tipps
Sie vergleichen Gleitkommazahlen. Tu das nicht, haben Gleitkommazahlen inhärente Präzision Fehler unter bestimmten Umständen. Stattdessen nehmen Sie den absoluten Wert der Differenz der beiden Werte und behauptet, dass der Wert geringer ist als einige wenige (epsilon).
void CompareFloats( double d1, double d2, double epsilon )
{
assert( abs( d1 - d2 ) < epsilon );
}
Das hat nichts mit dem Compiler und alles zu tun, mit der Art und Weise Gleitkommazahlen umgesetzt werden zu tun. Hier ist die IEEE-Spezifikation:
http://www.eecs.berkeley.edu/~wkahan/ ieee754status / IEEE754.PDF
Ich habe Ihr Problem auf meinem Intel Core 2 Duo dupliziert, und ich an dem Assembler-Code aussehen. Hier ist, was passiert: wenn Ihr Compiler t1 + t2
auswertet, tut es
load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum
Wenn es in sum_2
speichert es funktioniert
round the 80-bit sum to a 64-bit number and store it
Dann wird der ==
Vergleich vergleicht die 80-Bit-Summe auf eine 64-Bit-Summe, und sie sind unterschiedlich, vor allem wegen der Bruchteil 0,3 nicht genau dargestellt werden kann Gleitkommazahl eine binäre verwenden, so dass Sie ein vergleichen ' Wiederholen decimal‘(eigentlich Wiederholen binär), die auf zwei unterschiedlichen Längen abgeschnitten wurde.
Was wirklich irritierend ist, dass, wenn Sie Compiler mit gcc -O1
oder gcc -O2
, gcc tun die falsche Arithmetik bei der Kompilierung und das Problem weggeht. Vielleicht ist das OK nach dem Standard, aber es ist nur ein Grund mehr, dass gcc nicht mein Liebling Compiler ist.
P. S. Wenn ich sage, dass ==
eine 80-Bit vergleicht Summe mit einer 64-Bit-Summe, natürlich meine ich wirklich die erweiterte Version der Summe 64-Bit vergleicht. Sie könnten auch tun denken
sum_2 == t1 + t2
Entschlüssen zu
extend(sum_2) == extend(t1) + extend(t2)
und
sum_2 = t1 + t2
Entschlüssen zu
sum_2 = round(extend(t1) + extend(t2))
Willkommen in der wunderbaren Welt der Floating-Point!
Beim Vergleich von Gleitkommazahlen nach Nähen Sie in der Regel ihre relative Differenz messen wollen, das definiert ist als
if (abs(x) != 0 || abs(y) != 0)
rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
rel_diff(x,y) = max(abs(x),abs(y))
Beispiel:
rel_diff(1.12345, 1.12367) = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019
Die Idee ist, die Anzahl von führenden signifikanten Stellen zu messen, die Zahlen gemeinsam haben; wenn Sie die -log10 von ,000195787019 nehmen erhalten Sie 3,70821611, die über die Anzahl der führenden Basis 10 Ziffern sind alle Beispiele gemeinsam haben.
Wenn Sie benötigen, um zu bestimmen, ob zwei Gleitkommazahlen gleich sind, sollten Sie wie etwas tun
if (rel_diff(x,y) < error_factor * machine_epsilon()) then
print "equal\n";
, wo Maschine epsilon die kleinste Zahl, die in den Mantisse der Gleitkomma-Hardware gehalten werden können, verwendet werden. Die meisten Computersprachen haben einen Funktionsaufruf, diesen Wert zu erhalten. error_factor sollte auf der Anzahl der signifikanten Stellen stützen Sie denken, wird durch Rundungsfehler (und andere) verzehrt werden in den Berechnungen der Zahlen x und y. Zum Beispiel, wenn ich die x wusste und y waren das Ergebnis von etwa 1000 Additionen und wusste keine Grenzen auf den Zahlen summiert werden, ich error_factor auf etwa 100 gesetzt würde.
versucht, diese als Links hinzufügen, konnte aber nicht, da dies ist mein erster Beitrag:
- en.wikipedia.org/wiki/Relative_difference
- en.wikipedia.org/wiki/Machine_epsilon
- en.wikipedia.org/wiki/Significand (Mantisse)
- en.wikipedia.org/wiki/Rounding_error
Es kann sein, dass in einem der Fälle, Sie eine 64-Bit-Doppel auf einem 80-Bit-internen Register Vergleich enden. Es kann an der Montageanleitung GCC aussendet für die beiden Fälle ...
aussehen wird erleuchtet Ein Vergleich der Zahlen mit doppelter Genauigkeit ist von Natur aus ungenau. Zum Beispiel können Sie oft finden 0.0 == 0.0
Rückkehr false . Dies ist aufgrund der Art, wie die FPU speichert und Spuren Zahlen.
Die Prüfung auf Gleichheit ist problematisch. Zwei Computational Sequenzen, die mathematisch gleich sind können auch zu unterschiedlich Gleitkommawerte.
Sie müssen ein Dreieck verwenden, um eine Toleranz für Ihre Vergleiche zu geben, anstatt ein exakter Wert.
Dieses "Problem" kann "fixed", indem Sie diese Optionen:
-msse2 -mfpmath = sse
wie auf dieser Seite erläutert:
http://www.network-theory.co.uk /docs/gccintro/gccintro_70.html
Sobald ich diese Optionen verwendet, behauptet beide übergeben.