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?

War es hilfreich?

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.

Wikipedia sagt :

  

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top