Como señaló @Angew , el !=
operador necesita el mismo tipo en ambos lados.
(float)i != i
da como resultado la promoción del RHS para que flote también, así que lo hemos hecho (float)i != (float)i
.
g ++ también genera un bucle infinito, pero no optimiza el trabajo desde dentro. Puede ver que convierte int-> float con cvtsi2ss
y se ucomiss xmm0,xmm0
compara (float)i
consigo mismo. (Esa fue su primera pista de que su fuente de C ++ no significa lo que pensaba que hacía, como explica la respuesta de @ Angew).
x != x
solo es cierto cuando está "desordenado" porque x
era NaN. (se INFINITY
compara a sí mismo en matemáticas IEEE, pero NaN no. NAN == NAN
es falso, NAN != NAN
es verdadero).
gcc7.4 y versiones anteriores optimizan correctamente su código jnp
como la rama del bucle ( https://godbolt.org/z/fyOhW1 ): siga repitiendo mientras los operandos x != x
no sean NaN. (gcc8 y versiones posteriores también verifican je
una ruptura del bucle, sin optimizar en función del hecho de que siempre será cierto para cualquier entrada que no sea NaN). x86 FP compara set PF en desordenado.
Y por cierto, eso significa que la optimización de clang también es segura : solo tiene que CSE (float)i != (implicit conversion to float)i
como lo mismo, y demostrar que i -> float
nunca es NaN para el rango posible de int
.
(Aunque dado que este bucle llegará a UB de desbordamiento firmado, se le permite emitir literalmente cualquier conjunto que desee, incluida una ud2
instrucción ilegal, o un bucle infinito vacío independientemente de cuál sea el cuerpo del bucle en realidad). Pero ignorando el UB de desbordamiento firmado , esta optimización sigue siendo 100% legal.
GCC no puede optimizar el cuerpo del bucle incluso -fwrapv
para hacer que el desbordamiento de enteros con signo esté bien definido (como envoltura de complemento a 2). https://godbolt.org/z/t9A8t_
Incluso habilitar -fno-trapping-math
no ayuda. ( Desafortunadamente, el valor predeterminado de GCC es habilitar
-ftrapping-math
a pesar de que la implementación de GCC está rota / con errores ). Int-> conversión flotante puede causar una excepción FP inexacta (para números demasiado grandes para ser representados exactamente), por lo que con excepciones posiblemente desenmascaradas, es razonable no hacerlo optimizar el cuerpo del lazo. (Porque la conversión 16777217
a flotante podría tener un efecto secundario observable si se desenmascara la excepción inexacta).
Pero con -O3 -fwrapv -fno-trapping-math
, es una optimización 100% perdida no compilar esto en un bucle infinito vacío. Sin #pragma STDC FENV_ACCESS ON
, el estado de las banderas adhesivas que registran las excepciones de FP enmascaradas no es un efecto secundario observable del código. No int
-> la float
conversión puede resultar en NaN, por x != x
lo que no puede ser cierto.
Todos estos compiladores están optimizando para implementaciones de C ++ que usan IEEE 754 de precisión simple (binary32) float
y 32 bits int
.
El bucle corregido(int)(float)i != i
tendría UB en implementaciones de C ++ con 16 bits estrechos int
y / o más anchos float
, porque alcanzaría UB de desbordamiento de enteros con signo antes de llegar al primer entero que no era exactamente representable como float
.
Pero UB bajo un conjunto diferente de opciones definidas por la implementación no tiene consecuencias negativas cuando se compila para una implementación como gcc o clang con el x86-64 System V ABI.
Por cierto, podría calcular estáticamente el resultado de este bucle desde FLT_RADIX
y FLT_MANT_DIG
, definido en <climits>
. O al menos puede float
hacerlo en teoría, si realmente se ajusta al modelo de un flotante IEEE en lugar de algún otro tipo de representación de números reales como Posit / unum.
No estoy seguro de cuánto define el estándar ISO C ++ sobre el float
comportamiento y si un formato que no se basó en un exponente de ancho fijo y campos significativos sería compatible con los estándares.
En comentarios:
@geza ¡Me interesaría escuchar el número resultante!
@nada: es 16777216
¿Está afirmando que tiene este bucle para imprimir / devolver 16777216
?
Actualización: dado que ese comentario ha sido eliminado, creo que no. Probablemente el OP solo esté citando float
antes del primer número entero que no se puede representar exactamente como un 32 bits float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values, es decir, lo que esperaban verificar con este código defectuoso.
Por supuesto, la versión corregida se imprimirá 16777217
, el primer número entero que no es exactamente representable, en lugar del valor anterior.
(Todos los valores flotantes más altos son números enteros exactos, pero son múltiplos de 2, luego 4, luego 8, etc. para valores de exponentes más altos que el ancho significativo. Se pueden representar muchos valores enteros más altos, pero 1 unidad en el último lugar (del significado) es mayor que 1, por lo que no son números enteros contiguos. El finito más grande float
está justo por debajo de 2 ^ 128, que es demasiado grande para par int64_t
).
Si algún compilador saliera del bucle original e imprimiera eso, sería un error del compilador.