Bizarres Gleitkommaverhalten mit vs. ohne zusätzliche Variablen, warum?

8

Wenn ich den folgenden Code in VC ++ 2013 (32-Bit, keine Optimierungen) ausführen:

%Vor%

Ich bekomme

%Vor%

im Debugger.

Ich bin verrückt. Kann mir bitte jemand erklären wie die heck y1 und y2 unterschiedliche Werte haben?

Aktualisierung:

Dies scheint nur unter /Arch:SSE2 oder /Arch:AVX , nicht /Arch:IA32 oder /Arch:SSE zu geschehen.

    
Mehrdad 31.01.2014, 11:25
quelle

4 Antworten

4

Sie konvertieren out-of-range Werte für double in unsigned long long . Dies ist im Standard C ++ nicht erlaubt, und Visual C ++ scheint es im SSE2-Modus schlecht zu behandeln: Es hinterlässt eine Zahl auf dem FPU-Stack, überläuft sie schließlich und macht später Code, der die FPU verwendet, fehlgeschlagen wirklich interessante Wege.

Ein reduziertes Sample ist

%Vor%

Dies wird abgebrochen, wenn ull acht oder mehr Elemente hat, aber besteht, wenn es bis zu sieben hat.

Die Lösung besteht nicht darin, Fließkommawerte in einen ganzzahligen Typ zu konvertieren, es sei denn, Sie wissen, dass der Wert im Bereich liegt.

  

4.9 Floating-Integral-Konvertierungen [conv.fpint]

     

Ein Prvalue eines Fließkommatyps kann in einen prvalue eines Integer-Typs konvertiert werden. Die Konvertierung wird abgebrochen. das heißt, der Bruchteil wird verworfen. Das Verhalten ist nicht definiert, wenn der abgeschnittene Wert nicht im Zieltyp dargestellt werden kann. [ Hinweis: Wenn der Zieltyp bool ist, siehe 4.12. - Endnote ]

Die Regel, dass außerhalb des Bereichs liegende Werte beim Konvertieren in einen vorzeichenlosen Typ umbrochen werden, gilt nur, wenn der Wert eines Integer-Typs bereits vorhanden ist.

Was auch immer es wert ist, scheint jedoch nicht beabsichtigt zu sein. Obwohl der Standard dieses Verhalten zulässt, kann es dennoch sinnvoll sein, dies als Fehler zu melden.

    
hvd 31.01.2014, 20:00
quelle
4

9223372036854775808 ist 0x8000000000000000 ; das heißt, es ist INT64_MIN , das auf uint64_t umgewandelt wird.

Es sieht so aus, als würde Ihr Compiler den Rückgabewert von floor an long long übergeben und dann dieses Ergebnis an unsigned long long übergeben.

Beachten Sie, dass es für einen Überlauf bei der Konvertierung von Gleitkomma in Ganzzahl recht üblich ist, den am wenigsten darstellbaren Wert zu erhalten (z. B. cvttsd2siq auf x86-64):

  

Wenn eine Konvertierung ungenau ist, wird ein abgeschnittenes Ergebnis zurückgegeben. Wenn ein konvertiertes Ergebnis größer als die maximal signierte Doppelwort-Ganzzahl ist, wird die ungültige Gleitkommaausnahme ausgelöst, und wenn diese Ausnahme maskiert wird, wird der unbegrenzte Ganzzahlwert (80000000H) zurückgegeben.

(dies stammt aus der Doppelwort-Dokumentation, aber das Vierwort-Verhalten ist dasselbe.)

    
ecatmur 31.01.2014 11:53
quelle
3

Hypothese: Es ist ein Fehler. Der Compiler konvertiert double korrekt in unsigned long long , konvertiert aber Gleitkommazahl mit erweiterter Genauigkeit (möglicherweise long double ) in unsigned long long falsch. Details:

%Vor%

Dies berechnet den Wert auf der rechten Seite und speichert ihn in x . Der Wert auf der rechten Seite könnte mit erweiterter Genauigkeit berechnet werden, aber er wird, wie die Regeln von C ++ erfordern, in double konvertiert, wenn er in x gespeichert wird. Der exakte mathematische Wert wäre 9710908999008998870, aber das Runden auf das double -Format ergibt 9710908999008999424.

%Vor%

Dies konvertiert den double -Wert in x in unsigned long long und ergibt die erwartete 9710908999008999424.

%Vor%

Dies berechnet den Wert auf der rechten Seite mit erweiterter Genauigkeit und erzeugt 9710908999008998870. Wenn der Wert für die erweiterte Genauigkeit in unsigned long long konvertiert wird, gibt es einen Fehler, der 2 63 (9223372036854775808 ). Dieser Wert ist wahrscheinlich der Fehlerwert "außerhalb des Bereichs", der von einem Befehl erzeugt wird, der das Format mit erweiterter Genauigkeit in eine 64-Bit-Ganzzahl konvertiert. Der Compiler hat eine falsche Befehlssequenz verwendet, um das Format für die erweiterte Genauigkeit in unsigned long long zu konvertieren.

    
Eric Postpischil 31.01.2014 12:08
quelle
0

Du hast y1 als Doppelgegner gecasted, bevor du es erneut zu einem langen Wurf verwandelst. Der Wert von x ist nicht der "floor" -Wert, sondern ein gerundeter Wert für floor.

Die gleiche Logik würde bei Casting-Integern und -Floats gelten. float x = (float) ((int) 1.5) gibt float x = 1.5

einen anderen Wert     
James Malone 31.01.2014 11:35
quelle