Ich kompiliere und führe das folgende Programm auf 32- und 64-Bit-Plattformen aus:
int main()
{
double y = 8.34214e08;
double z = 1.25823e45;
return y * z == 8.34214e08 * 1.25823e45;
}
Während in 64-Bit das Ergebnis das erwartete ist (die Werte sind gleich und der Exit-Code ist ungleich Null), scheint es in 32-Bit einen kleinen Unterschied zwischen dem zur Kompilierzeit berechneten Wert, der rechten Seite des Vergleichs und der linken zu geben Seite zur Laufzeit berechnet.
Ist das ein Fehler im Compiler oder gibt es eine logische Erklärung?
BEARBEITEN: Dies unterscheidet sich von Warum der Vergleich von Double und Float zu einem unerwarteten Ergebnis führt? denn hier sind alle Werte doppelt.
IEEE-754 ermöglicht Zwischenberechnungen mit größerer Genauigkeit (Hervorhebung von mir).
(IEEE-754:2008) „Ein Sprachstandard sollte auch Attribute definieren und von Implementierungen verlangen, dass diese Attribute bereitstellen, die wertverändernde Optimierungen für einen Block separat oder kollektiv zulassen oder verbieten. Diese Optimierungen können Folgendes umfassen, sind aber nicht darauf beschränkt: […] Nutzung breiterer Zwischenergebnisse bei der Ausdrucksauswertung.”
In Ihrem Fall beispielsweise auf einem IA-32 könnten die Double-Werte mit größerer Genauigkeit in den x87-FPU-Registern gespeichert werden (80-Bit statt 64). Sie vergleichen also tatsächlich eine Multiplikation mit doppelter Genauigkeit mit einer Multiplikation mit doppelt erweiterter Genauigkeit.
Zum Beispiel auf x64, wo das Ergebnis ist 1
(Die x87-FPU wird nicht verwendet, da stattdessen SSE verwendet wird), Hinzufügen gcc
Möglichkeit -mfpmath=387
Die Verwendung von x87 führt zu einer Änderung des Ergebnisses 0
auf meiner Maschine.
Und wenn Sie sich fragen, ob das auch von C erlaubt ist, ist es:
(C99, 6.3.1.p8) “Die Werte von Floating-Operanden und der Ergebnisse von Floating-Ausdrücken können mit größerer Genauigkeit und größerem Bereich dargestellt werden, als es der Typ erfordert;”
Im Algemeinen, noch nie tun Gleichheitsprüfungen mit Fließkommazahlen. Sie müssen prüfen, ob das gewünschte Ergebnis weniger als eine voreingestellte Genauigkeit von dem erhaltenen Ergebnis abweicht.
Was hier passiert, ist höchstwahrscheinlich darauf zurückzuführen, dass die Multiplikation auf zwei verschiedenen “Plattformen” ausgeführt wird: einmal von Ihrem Code und einmal vom Compiler, der möglicherweise eine unterschiedliche Genauigkeit hat. Dies geschieht mit die meisten Compiler.
Ihr Programm würde wahrscheinlich funktionieren, wenn Sie es mit kompiliert haben dieselben Optionen, die zum Kompilieren des Compilers verwendet wurden (vorausgesetzt, der Compiler wurde selbst kompiliert). Aber das würde nicht bedeuten, dass du das bekommen würdest Korrekt Ergebnis; Sie würden den gleichen Genauigkeitsfehler erhalten, den der Compiler erhält.
(Außerdem gehe ich davon aus, dass der Compiler eine gerade Multiplikation durchführt und Der Parsing-Code, der Gleitkommazahlen erkennt, geht nicht in die Gleichung ein. Das mag Wunschdenken meinerseits sein).
Testen
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-suse-linux/4.8/lto-wrapper
Target: x86_64-suse-linux
Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.8 --enable-ssp --disable-libssp --disable-plugin --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --enable-linker-build-id --enable-linux-futex --program-suffix=-4.8 --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux --host=x86_64-suse-linux
Thread model: posix
gcc version 4.8.3 20141208 [gcc-4_8-branch revision 218481] (SUSE Linux)
#include <stdio.h>
int main()
{
double y = 8.34214e08;
double z = 1.25823e45;
return printf("%s\n", y * z == 8.34214e08 * 1.25823e45 ? "Equal" : "NOT equal!");
}
Wenn wir -O0 zwingen, um zu verhindern, dass der Compiler den gesamten Code optimiert (danke @markgz!), erhalten wir
$ gcc -m32 -O0 -o float float.c && ./float NICHT gleich! $ gcc -m32 -frounding-math -O0 -o float float.c && ./float Gleich
Fürs Protokoll, da du vor mir da warst :-),
-grunding-math
Deaktivieren Sie Transformationen und Optimierungen, die das standardmäßige Gleitkomma-Rundungsverhalten annehmen. Dies ist Runden auf Null für alle Umwandlungen von Gleitkommazahlen in Ganzzahlen und Runden auf den nächsten Wert für alle anderen arithmetischen Kürzungen. Diese Option sollte für Programme angegeben werden, die den FP-Rundungsmodus dynamisch ändern oder die mit einem nicht standardmäßigen Rundungsmodus ausgeführt werden können. Diese Option deaktiviert das ständige Falten von Gleitkommaausdrücken zur Kompilierzeit (kann vom Rundungsmodus beeinflusst werden) und arithmetische Transformationen, die bei Vorhandensein von vorzeichenabhängigen Rundungsmodi unsicher sind.
Der Standardwert ist -fno-rounding-math.
Gleitkommaberechnungen, die zur Kompilierzeit durchgeführt werden, treten häufig mit einer höheren Genauigkeit als auf double
zur Laufzeit verwendet. Auch C kann Laufzeit-Zwischenleistungen erbringen double
Berechnungen nach oben long double
Präzision. Erklären Sie entweder Ihre Ungleichheit. Sehen FLT_EVAL_METHOD
für Details.
volatile double y = 8.34214e08;
volatile double z = 1.25823e45;
volatile double yz = 8.34214e08 * 1.25823e45;
printf("%.20e\n", y);
printf("%.20e\n", z);
printf("%.20e\n", yz);
printf("%.20Le\n", (long double) y*z);
printf("%.20Le\n", (long double) 8.34214e08 * 1.25823e45);
8.34214000000000000000e+08
1.25822999999999992531e+45
// 3 different products!
1.04963308121999993395e+54
1.04963308121999993769e+54
1.04963308122000000000e+54
Ihre Ergebnisse können leicht abweichen.
@waj: Ich habe deine Frage nicht abgelehnt. Es ist eine gute Frage.
– Steve Summit
14. Juli 2015 um 22:13 Uhr
@SteveSummit ok, dann tut mir leid, ich habe die Ablehnung zur gleichen Zeit erhalten, als Sie geantwortet haben, und StackOverflow zeigt nicht, wer abgestimmt hat. Wie auch immer, ich kann damit leben, aber ich muss wirklich die Gründe wissen, deshalb schicke ich hier überhaupt eine Frage.
– waj
14. Juli 2015 um 22:16 Uhr
@Giorgi: In dieser Frage gibt es einen Unterschied, weil die Variable als “float” deklariert ist und die Literale “double” sind. Hier sind alle Werte doppelt.
– waj
14. Juli 2015 um 22:19 Uhr
@waj mit gcc 5.1 gibt es 1 auf amd64 sowie armv7h (ARM 32 bit). Welchen Compiler verwenden Sie?
– woggioni
14. Juli 2015 um 22:21 Uhr
gcc scheint das Programm nach unten zu optimieren
return 1
mit jeder darüber liegenden Optimierungsstufe-O0
.– markgz
14. Juli 2015 um 22:39 Uhr