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?

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.
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.
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