Wie @Angew hervorhob , benötigt der !=
Bediener auf beiden Seiten den gleichen Typ.
(float)i != i
führt zu einer Förderung der RHS, um auch zu schweben, so haben wir (float)i != (float)i
.
g ++ generiert auch eine Endlosschleife, optimiert jedoch nicht die Arbeit von innen heraus. Sie können sehen, dass es int-> float mit konvertiert cvtsi2ss
und mit sich selbst ucomiss xmm0,xmm0
vergleicht (float)i
. (Das war Ihr erster Hinweis darauf, dass Ihre C ++ - Quelle nicht das bedeutet, was Sie für die Antwort von @ Angew gehalten haben.)
x != x
ist nur wahr, wenn es "ungeordnet" ist, weil x
NaN war. ( INFINITY
Vergleiche in der IEEE-Mathematik gleich, aber NaN nicht. NAN == NAN
ist falsch, NAN != NAN
ist wahr).
gcc7.4 und älter optimiert Ihren Code korrekt jnp
als Schleifenzweig ( https://godbolt.org/z/fyOhW1 ): Führen Sie eine Schleife durch, solange die Operanden x != x
nicht NaN waren. (gcc8 und höher prüfen auch, ob je
die Schleife unterbrochen wurde, und können nicht optimieren, da dies für alle Nicht-NaN-Eingaben immer der Fall ist.) x86 FP vergleicht den eingestellten PF mit ungeordnet.
Übrigens, das bedeutet, dass die Optimierung von clang auch sicher ist : Es muss nur CSE (float)i != (implicit conversion to float)i
als gleich sein und beweisen, dass i -> float
es für den möglichen Bereich von niemals NaN ist int
.
(Obwohl diese Schleife UB mit vorzeichenbehaftetem Überlauf trifft, darf sie buchstäblich jeden gewünschten Asm ausgeben, einschließlich einer ud2
illegalen Anweisung oder einer leeren Endlosschleife, unabhängig davon, was der Schleifenkörper tatsächlich war.) Ignoriert jedoch den UB mit vorzeichenbehaftetem Überlauf ist diese Optimierung noch zu 100% legal.
GCC kann den Schleifenkörper nicht optimieren, selbst wenn der -fwrapv
Überlauf mit vorzeichenbehafteten Ganzzahlen genau definiert ist (als 2er-Komplement-Wraparound). https://godbolt.org/z/t9A8t_
Auch das Aktivieren -fno-trapping-math
hilft nicht. (Die Standardeinstellung von GCC ist leider die Aktivierung
-ftrapping-math
, obwohl die Implementierung von GCC fehlerhaft / fehlerhaft ist .) Die Int-> Float-Konvertierung kann eine ungenaue FP-Ausnahme verursachen (für Zahlen, die zu groß sind, um genau dargestellt zu werden). Mit möglicherweise nicht maskierten Ausnahmen ist es daher sinnvoll, dies nicht zu tun Optimieren Sie den Schleifenkörper. (Da die Konvertierung 16777217
in Float einen beobachtbaren Nebeneffekt haben kann, wenn die ungenaue Ausnahme nicht maskiert ist.)
Aber mit -O3 -fwrapv -fno-trapping-math
, es ist 100% verpasste Optimierung, dies nicht zu einer leeren Endlosschleife zu kompilieren. Ohne #pragma STDC FENV_ACCESS ON
ist der Status der Sticky-Flags, die maskierte FP-Ausnahmen aufzeichnen, kein beobachtbarer Nebeneffekt des Codes. Nein int
-> float
Konvertierung kann zu NaN führen, x != x
kann also nicht wahr sein.
Diese Compiler sind alle für C ++ - Implementierungen optimiert, die IEEE 754 Single-Precision (Binary32) float
und 32-Bit verwenden int
.
Die Bugfixed-(int)(float)i != i
Schleife hätte UB in C ++ - Implementierungen mit schmalem 16-Bit int
und / oder breiter float
, da Sie vor dem Erreichen der ersten Ganzzahl, die nicht genau als dargestellt werden konnte, den Überlauf UB mit vorzeichenbehafteten Ganzzahlen erreichen würden float
.
UB unter einer anderen Reihe von implementierungsdefinierten Auswahlmöglichkeiten hat jedoch keine negativen Konsequenzen beim Kompilieren für eine Implementierung wie gcc oder clang mit dem x86-64 System V ABI.
Übrigens können Sie das Ergebnis dieser Schleife statisch aus FLT_RADIX
und berechnen FLT_MANT_DIG
, definiert in <climits>
. Zumindest können Sie dies theoretisch float
tun , wenn es tatsächlich zum Modell eines IEEE-Floats passt und nicht zu einer anderen Art der Darstellung reeller Zahlen wie Posit / Unum.
Ich bin mir nicht sicher, wie sehr sich der ISO C ++ - Standard auf das float
Verhalten auswirkt und ob ein Format, das nicht auf Exponentenfeldern mit fester Breite und Signifikantenfeldern basiert, standardkonform wäre.
In Kommentaren:
@geza Ich würde mich freuen, die resultierende Nummer zu hören!
@nada: Es ist 16777216
Wollen Sie behaupten, Sie hätten diese Schleife zum Drucken / Zurückgeben 16777216
?
Update: Da dieser Kommentar gelöscht wurde, denke ich nicht. Wahrscheinlich zitiert das OP nur die float
vor der ersten Ganzzahl, die nicht genau als 32-Bit dargestellt werden kann float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values, dh was sie mit diesem fehlerhaften Code überprüfen wollten.
Die Bugfixed-Version würde natürlich drucken 16777217
, die erste Ganzzahl, die nicht genau darstellbar ist, und nicht der Wert davor.
(Alle höheren Float-Werte sind exakte Ganzzahlen, aber sie sind Vielfache von 2, dann 4, dann 8 usw. für Exponentenwerte, die höher als die Signifikantenbreite sind. Viele höhere Ganzzahlwerte können dargestellt werden, aber 1 Einheit an letzter Stelle (des Signifikanten) ist größer als 1, es handelt sich also nicht um zusammenhängende ganze Zahlen. Die größte Endlichkeit float
liegt knapp unter 2 ^ 128, was für gerade zu groß ist int64_t
.)
Wenn ein Compiler die ursprüngliche Schleife verlassen und diese drucken würde, wäre dies ein Compiler-Fehler.