Come ha sottolineato @Angew , l' !=operatore necessita dello stesso tipo su entrambi i lati.
(float)i != isi 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 cvtsi2sse fa il ucomiss xmm0,xmm0confronto (float)icon 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é xera NaN. ( INFINITYconfronta 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 jnpcome 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 jeun'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)icome essere lo stesso, e dimostrare che i -> floatnon è 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 ud2un'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 -fwrapvper 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-mathnon aiuta. (L'impostazione predefinita di GCC è sfortunatamente di abilitare
-ftrapping-mathanche 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 16777217in 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 floatconversione può risultare in NaN, quindi x != xnon può essere vero.
Questi compilatori sono tutti ottimizzati per le implementazioni C ++ che utilizzano IEEE 754 a precisione singola (binary32) floate 32 bit int.
Il ciclo corretto da bug(int)(float)i != i avrebbe UB su implementazioni C ++ con 16 bit stretti inte / 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_RADIXe FLT_MANT_DIG, definito in <climits>. O almeno puoi in teoria, se in floatrealtà 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 floatcomportamento 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 floatprima 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.