Die doppelte Multiplikation unterscheidet sich zwischen Kompilierzeit und Laufzeit auf einer 32-Bit-Plattform

Lesezeit: 6 Minuten

Benutzer-Avatar
waj

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.

  • @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

Benutzer-Avatar
ouah

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;”

  • Gute Erklärung über breitere Zwischenergebnisse.

    – chux – Wiedereinsetzung von Monica

    14. Juli 2015 um 23:00 Uhr

Benutzer-Avatar
LSerni

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.

  • Vielleicht sollte ich klarstellen, dass ich die Kompilierung und Ausführung jedes Mal auf demselben Computer ausführe. Ich stimme voll und ganz zu, dass Gleichheit im Allgemeinen nicht für Gleitkommazahlen verwendet werden sollte.

    – waj

    14. Juli 2015 um 22:27 Uhr


  • Ich wollte gerade so etwas beantworten, aber was mich wirklich verwirrt, ist der Unterschied zwischen Kompilierzeit- und Laufzeitberechnung. Ich hätte gedacht, dass GCC die Berechnung zur Kompilierungszeit mit der gleichen Genauigkeit / den gleichen Einstellungen wie bei der Berechnung zur Laufzeit oder was auch immer durchgeführt hat, aber irgendwie unterscheidet sich das Ergebnis 🙂 Ich bin genauso verwirrt wie @waj

    – Morten Jensen

    14. Juli 2015 um 22:28 Uhr

  • Ich erinnere mich, dass ich seltsame Ergebnisse mit bekommen habe gcc Vor einigen Jahren hatte ich aufgrund einiger mathematischer “Optimierungen” übersehen. Es gibt mehrere “Gleitkomma”-Standards und zum einen unterstützt gcc alle von ihnen. Natürlich lief gcc nur mit einem – dem, mit dem es selbst kompiliert wurde. Und ich war beim Aufbau der Toolchain vielleicht etwas, ähm, zu freizügig. Mein Professor war Ja wirklich nicht amüsiert 🙂

    – LSerni

    14. Juli 2015 um 22:38 Uhr

  • Sie haben Recht, ich habe erneut mit -frounding-math kompiliert und die Ergebnisse sind jetzt dieselben. Mehr Infos habe ich hier gefunden: gcc.gnu.org/wiki/…

    – waj

    14. Juli 2015 um 22:42 Uhr

  • Gcc optimiert die Berechnung mit jedem Optimierungsniveau über -O0 (aufgrund der konstanten Faltung) vollständig auf “wahr”. Man müsste die ständige Faltungsoptimierung von gcc mit einem komplexeren Programm täuschen, um die ursprüngliche Frage des OP zu testen.

    – markgz

    14. Juli 2015 um 23:00 Uhr

Benutzer-Avatar
Chux – Wiedereinsetzung von Monica

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.

1174980cookie-checkDie doppelte Multiplikation unterscheidet sich zwischen Kompilierzeit und Laufzeit auf einer 32-Bit-Plattform

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy