Unterschied in der Gleitkommaarithmetik zwischen x86 und x64

Lesezeit: 5 Minuten

Ich bin auf einen Unterschied in der Art und Weise gestoßen, wie Gleitkomma-Arithmetik zwischen MS VS 2010-Builds für x86 und x64 durchgeführt wird (beide werden auf demselben 64-Bit-Computer ausgeführt).

Dies ist ein reduziertes Codebeispiel:

float a = 50.0f;
float b = 65.0f;
float c =  1.3f;
float d = a*c;
bool bLarger1 = d<b;
bool bLarger2 = (a*c)<b;

Der boolesche bLarger1 ist immer falsch (d ist in beiden Builds auf 65,0 gesetzt). Die Variable bLarger2 ist falsch für x64, aber wahr für x86!

Ich bin mir der Gleitkomma-Arithmetik und der auftretenden Rundungseffekte bewusst. Ich weiß auch, dass 32-Bit manchmal andere Anweisungen für Floating-Operationen verwendet als 64-Bit-Builds. Aber in diesem Fall fehlen mir einige Informationen.

Warum gibt es überhaupt eine Diskrepanz zwischen bLarger1 und bLarger2? Warum ist es nur auf dem 32-Bit-Build vorhanden?

Links: x86, rechts: x64

  • Ich vermute, dass die x86-Version dafür die FPU-Register verwendet und x64 dafür die SSE-Register. Aber Sie müssen sich wahrscheinlich den IL-Code und auch den Maschinencode ansehen.

    – Lew

    28. März 2014 um 10:37 Uhr


  • Sicherlich verwendet x86 die x87-Einheit und x64 verwendet die SSE-Einheit. Aber es erklärt nicht wirklich den Unterschied. Sie sollten beide die gleiche Antwort erhalten. @Oliver kannst du zeigen, wie du den Code kompilierst, weil mein ziemlicher Versuch einer Repro fehlgeschlagen ist. Beide booleschen Werte sind false für x86 und x64 für mich.

    – David Heffernan

    28. März 2014 um 10:39 Uhr

  • So wird a*c im Ausdruck for behandelt bLarger2. Ich schätze, es wird ein Float-Mult in dem einen und ein Doppel-Mult in dem anderen oder so ähnlich sein

    – David Heffernan

    28. März 2014 um 10:49 Uhr


  • Die eigentliche Frage ist, warum das überraschend ist, obwohl man genau weiß, dass Gleitkommazahlen nicht exakt sind. Je nach Compiler, Compileroptionen usw. können die Ergebnisse abweichen.

    – Paul McKenzie

    28. März 2014 um 10:53 Uhr


  • @PaulMcKenzie Während die Gleitkommaarithmetik nicht alle realen Werte genau darstellt, ist sie wiederholbar und gut definiert. Es ist nicht unvernünftig, auf Konsistenz zwischen verschiedenen Compilern zu hoffen.

    – David Heffernan

    28. März 2014 um 11:29 Uhr

Unterschied in der Gleitkommaarithmetik zwischen x86 und
David Heffernan

Das Problem hängt von diesem Ausdruck ab:

bool bLarger2 = (a*c)<b;

Ich habe mir den unter VS2008 generierten Code angesehen, ohne VS2010 zur Hand zu haben. Für 64-Bit lautet der Code:

000000013FD51100  movss       xmm1,dword ptr [a] 
000000013FD51106  mulss       xmm1,dword ptr [c] 
000000013FD5110C  movss       xmm0,dword ptr [b] 
000000013FD51112  comiss      xmm0,xmm1 

Für 32 Bit lautet der Code:

00FC14DC  fld         dword ptr [a] 
00FC14DF  fmul        dword ptr [c] 
00FC14E2  fld         dword ptr [b] 
00FC14E5  fcompp           

Unter 32 Bit wird die Berechnung also in der x87-Einheit durchgeführt und unter 64 Bit wird sie von der x64-Einheit durchgeführt.

Und der Unterschied hier ist, dass die x87-Operationen alle mit höherer als einfacher Genauigkeit ausgeführt werden. Standardmäßig werden die Berechnungen mit doppelter Genauigkeit durchgeführt. Andererseits sind die SSE-Einheitsoperationen reine Berechnungen mit einfacher Genauigkeit.

Sie können die 32-Bit-Einheit dazu bringen, alle Berechnungen mit einer Genauigkeit von einfacher Genauigkeit wie folgt durchzuführen:

_controlfp(_PC_24, _MCW_PC);

Wenn Sie das zu Ihrem 32-Bit-Programm hinzufügen, werden Sie feststellen, dass die booleschen Werte beide auf “false” gesetzt sind.

Es gibt einen grundlegenden Unterschied in der Funktionsweise von x87- und SSE-Gleitkommaeinheiten. Die x87-Einheit verwendet die gleichen Anweisungen für Typen mit einfacher und doppelter Genauigkeit. Daten werden in Register im x87-FPU-Stapel geladen, und diese Register sind immer 10 Byte Intel-erweitert. Sie können die Genauigkeit mit dem Fließkomma-Steuerwort steuern. Aber die Anweisungen, die der Compiler schreibt, kennen diesen Zustand nicht.

Andererseits verwendet die SSE-Einheit unterschiedliche Anweisungen für Operationen mit einfacher und doppelter Genauigkeit. Das bedeutet, dass der Compiler Code ausgeben kann, der die volle Kontrolle über die Genauigkeit der Berechnung hat.

Die x87-Einheit ist hier also der Bösewicht. Sie können vielleicht versuchen, Ihren Compiler davon zu überzeugen, SSE-Anweisungen auch für 32-Bit-Ziele auszugeben. Und als ich Ihren Code unter VS2013 kompiliert habe, habe ich festgestellt, dass sowohl 32- als auch 64-Bit-Ziele SSE-Anweisungen ausgeben.

  • Interessant, daher gehe ich davon aus, dass der Unterschied zwischen der Berechnung von bLarger1 und bLarger2 in der Genauigkeit der ALU liegt. Für bLarger2 wird das Ergebnis mit der höheren Genauigkeit zum Vergleich in der ALU gehalten, während bLarger1 zuerst die Werte mit einfacher Genauigkeit lädt und diese vergleicht.

    – Oliver Zendel

    28. März 2014 um 11:24 Uhr

  • @OliverZendel Richtig

    – David Heffernan

    28. März 2014 um 11:28 Uhr

  • Hm, weder das Ändern von “Enable Enhanced Instruction Set” noch von “Floating Point Model” führte zu einem anderen Verhalten (VS2010 x86); _controlfp macht den Trick, aber ich bin immer noch neugierig, ob es eine Compiler-Einstellung selbst gibt, die zu demselben Verhalten führt

    – Oliver Zendel

    28. März 2014 um 11:48 Uhr


  • @OliverZendel Es gibt keine Compiler-Einstellung, die das Steuerwort ändert. Das ist eine Laufzeiteigenschaft und muss daher zur Laufzeit verwaltet werden. Und es ist eine totale Minenfeld-FWIW mit Modulen links rechts und Mitte, die mit dem Steuerwort schrauben. Kompilieren mit /arch:SSE2 und Sie vermeiden all diesen x87-Schmerz. Aber Ihr Programm läuft nur auf Maschinen mit SSE2-Einheiten.

    – David Heffernan

    28. März 2014 um 12:00 Uhr

  • Eigentlich bin ich mir nicht einmal sicher, ob /arch:SSE2 es unbedingt schaffen wird. Wie auch immer, ich denke, wir verstehen zumindest die Ursache für das Verhalten.

    – David Heffernan

    28. März 2014 um 12:18 Uhr


Gleitkommaoperationen sind immer ungenau, und der Vergleich zweier Gleitkommazahlen, die so nahe (oder gleich) sind, gibt fast nie die richtige Ausgabe zurück.

Gleitkommazahlen werden auf 32-Bit- und 64-Bit-Computern unterschiedlich gespeichert und verarbeitet (wie auch von Kommentaren vorgeschlagen). Wenn ich mich richtig erinnere, werden in VC 32-Bit-Floats auf dem Stack gespeichert und von der FPU (Floating-Point Unit) verarbeitet, während Floats auf einer 64-Bit-Maschine in spezialisierten Registern (SSE) gespeichert und mit anderen Einheiten in der CPU berechnet werden können.

Ich habe keine eindeutige Quelle für meine Antwort, aber bitte schauen Sie nach diese Seite oder Das.

  • Bei der Lagerung gibt es keinen Unterschied. Dies sind IEEE754-Floats. Ein Standardformat.

    – David Heffernan

    28. März 2014 um 11:11 Uhr

  • @DavidHeffernan, Sie werden auf die gleiche Weise gespeichert, aber ich versuche zu sagen, dass es sich um unterschiedliche Orte handelt.

    – Janman

    28. März 2014 um 11:30 Uhr

  • Schwimmer werden jedoch nicht unbedingt auf dem Stack gespeichert. Die x87-Einheit hat ihren eigenen Registerstapel von 8 Gleitkommaregistern ST(0) bis ST(7) mit erweiterter Genauigkeit. Sie sind mit Sicherheit spezialisierte Register. Und die SSE-Einheit hat ihre eigenen spezialisierten Register.

    – David Heffernan

    28. März 2014 um 11:40 Uhr

915070cookie-checkUnterschied in der Gleitkommaarithmetik zwischen x86 und x64

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

Privacy policy