Come ha sottolineato @Angew , l' !=
operatore necessita dello stesso tipo su entrambi i lati.
(float)i != i
si traduce anche nella promozione della RHS a galleggiare, quindi abbiamo (float)i != (float)i
.
g ++ genera anche un ciclo infinito, ma non ottimizza il lavoro al suo interno. Puoi vedere che converte int-> float con cvtsi2ss
e fa il ucomiss xmm0,xmm0
confronto (float)i
con se stesso. (Questo è stato il tuo primo indizio che la tua fonte C ++ non significa quello che pensavi che piacesse la risposta di @ Angew spiega.)
x != x
è vero solo quando è "non ordinato" perché x
era NaN. ( INFINITY
confronta uguale a se stesso in matematica IEEE, ma NaN no. NAN == NAN
è falso, NAN != NAN
è vero).
gcc7.4 e versioni precedenti ottimizzano correttamente il codice jnp
come ramo del ciclo ( https://godbolt.org/z/fyOhW1 ): continua a eseguire il ciclo finché gli operandi x != x
non erano NaN. (gcc8 e versioni successive verificano anche je
un'interruzione del ciclo, non riuscendo a ottimizzare in base al fatto che sarà sempre vero per qualsiasi input non NaN). x86 FP confronta set PF su non ordinato.
E a proposito, ciò significa che anche l'ottimizzazione di clang è sicura : deve solo CSE (float)i != (implicit conversion to float)i
come essere lo stesso, e dimostrare che i -> float
non è mai NaN per la gamma possibile di int
.
(Anche se dato che questo ciclo raggiungerà UB con overflow con segno, è consentito emettere letteralmente qualsiasi asm desideri, inclusa ud2
un'istruzione illegale o un ciclo infinito vuoto indipendentemente da quale fosse effettivamente il corpo del ciclo.) Ma ignorando l'UB di overflow con segno. , questa ottimizzazione è ancora legale al 100%.
GCC non riesce a ottimizzare il corpo del ciclo anche -fwrapv
per rendere ben definito l'overflow di interi con segno (come avvolgimento del complemento di 2). https://godbolt.org/z/t9A8t_
Anche l'abilitazione -fno-trapping-math
non aiuta. (L'impostazione predefinita di GCC è sfortunatamente di abilitare
-ftrapping-math
anche se l'implementazione di GCC è difettosa / buggy .) La conversione int-> float può causare un'eccezione inesatta FP (per numeri troppo grandi per essere rappresentati esattamente), quindi con eccezioni eventualmente smascherate è ragionevole non farlo ottimizzare il corpo del ciclo. (Perché la conversione 16777217
in float potrebbe avere un effetto collaterale osservabile se l'eccezione inesatta viene smascherata.)
Ma con -O3 -fwrapv -fno-trapping-math
, è un'ottimizzazione mancata al 100% per non compilare questo in un ciclo infinito vuoto. Senza #pragma STDC FENV_ACCESS ON
, lo stato dei flag permanenti che registrano le eccezioni FP mascherate non è un effetto collaterale osservabile del codice. No int
-> la float
conversione può risultare in NaN, quindi x != x
non può essere vero.
Questi compilatori sono tutti ottimizzati per le implementazioni C ++ che utilizzano IEEE 754 a precisione singola (binary32) float
e 32 bit int
.
Il ciclo corretto da bug(int)(float)i != i
avrebbe UB su implementazioni C ++ con 16 bit stretti int
e / o più larghi float
, perché avresti raggiunto UB con overflow del numero intero firmato prima di raggiungere il primo numero intero che non era esattamente rappresentabile come file float
.
Ma UB in un diverso insieme di scelte definite dall'implementazione non ha conseguenze negative quando si compila per un'implementazione come gcc o clang con l'ABI System V x86-64.
BTW, potresti calcolare staticamente il risultato di questo ciclo da FLT_RADIX
e FLT_MANT_DIG
, definito in <climits>
. O almeno puoi in teoria, se in float
realtà si adatta al modello di un float IEEE piuttosto che a qualche altro tipo di rappresentazione in numero reale come un Posit / unum.
Non sono sicuro di quanto lo standard ISO C ++ definisca il float
comportamento e se un formato che non fosse basato su campi di esponente e significato a larghezza fissa sarebbe conforme agli standard.
Nei commenti:
@geza sarei interessato a sentire il numero risultante!
@nada: è 16777216
Stai affermando di avere questo ciclo da stampare / restituire 16777216
?
Aggiornamento: poiché quel commento è stato cancellato, credo di no. Probabilmente l'OP sta solo citando la float
prima del primo numero intero che non può essere rappresentato esattamente come un 32 bit float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values, ovvero cosa speravano di verificare con questo codice difettoso.
La versione con bugfix sarebbe ovviamente stampata 16777217
, il primo intero che non è esattamente rappresentabile, piuttosto che il valore precedente.
(Tutti i valori in virgola mobile più alti sono numeri interi esatti, ma sono multipli di 2, poi 4, poi 8, ecc. Per i valori di esponente superiori alla larghezza del significato. Possono essere rappresentati molti valori interi più alti, ma 1 unità nell'ultimo posto (del significante) è maggiore di 1, quindi non sono numeri interi contigui. Il finito più grande float
è appena inferiore a 2 ^ 128, che è troppo grande per pari int64_t
.)
Se un compilatore uscisse dal ciclo originale e lo stampasse, sarebbe un bug del compilatore.