Como @Angew apontou , o !=
operador precisa do mesmo tipo em ambos os lados.
(float)i != i
resulta na promoção do RHS para flutuar também, então fizemos (float)i != (float)i
.
g ++ também gera um loop infinito, mas não otimiza o trabalho de dentro dele. Você pode ver que ele converte int-> float com cvtsi2ss
e faz ucomiss xmm0,xmm0
para comparar (float)i
consigo mesmo. (Essa foi sua primeira pista de que seu código-fonte C ++ não significa o que você achou que ele gostava de @resposta de Angew explica.)
x != x
só é verdadeiro quando está "desordenado" porque x
era NaN. ( INFINITY
compara igual a si mesmo na matemática IEEE, mas NaN não. NAN == NAN
é falso, NAN != NAN
é verdadeiro).
gcc7.4 e mais antigos otimizam corretamente seu código jnp
como o ramo do loop ( https://godbolt.org/z/fyOhW1 ): mantenha o loop enquanto os operandos x != x
não forem NaN. (gcc8 e posterior também verifica je
se há uma quebra do loop, deixando de otimizar com base no fato de que sempre será verdadeiro para qualquer entrada não NaN). x86 FP compara PF definido em não ordenado.
E, a propósito, isso significa que a otimização do clang também é segura : ele apenas precisa CSE (float)i != (implicit conversion to float)i
como sendo o mesmo, e provar que i -> float
nunca é NaN para o intervalo possível de int
.
(Embora dado que este loop atingirá UB de estouro assinado, é permitido emitir literalmente qualquer asm que desejar, incluindo uma ud2
instrução ilegal ou um loop infinito vazio, independentemente do que o corpo do loop realmente era.) Mas ignorando o UB de estouro assinado , essa otimização ainda é 100% legal.
O GCC falha em otimizar o corpo do loop, mesmo -fwrapv
para tornar o estouro de inteiro com sinal bem definido (como complemento de 2). https://godbolt.org/z/t9A8t_
Mesmo habilitando -fno-trapping-math
não ajuda. (O padrão do GCC infelizmente é habilitar
-ftrapping-math
mesmo que a implementação do GCC esteja quebrada / com erros .) Int-> conversão float pode causar uma exceção inexata de FP (para números muito grandes para serem representados exatamente), então, com exceções possivelmente desmascaradas, é razoável não otimize o corpo do loop. (Porque a conversão 16777217
para float pode ter um efeito colateral observável se a exceção inexata for desmascarada.)
Mas com -O3 -fwrapv -fno-trapping-math
, é 100% de otimização perdida não compilar isso em um loop infinito vazio. Sem #pragma STDC FENV_ACCESS ON
, o estado dos sinalizadores fixos que registram exceções de FP mascaradas não é um efeito colateral observável do código. Não int
-> float
conversão pode resultar em NaN, então x != x
não pode ser verdade.
Todos esses compiladores são otimizados para implementações C ++ que usam IEEE 754 de precisão única (binary32) float
e 32 bits int
.
O loop corrigido de bug(int)(float)i != i
teria UB em implementações C ++ com estreito de 16 bits int
e / ou mais amplo float
, porque você atingiu o UB de estouro de inteiro com sinal antes de atingir o primeiro inteiro que não era exatamente representável como a float
.
Mas o UB sob um conjunto diferente de opções definidas pela implementação não tem consequências negativas ao compilar para uma implementação como gcc ou clang com o x86-64 System V ABI.
BTW, você poderia calcular estaticamente o resultado deste loop de FLT_RADIX
e FLT_MANT_DIG
, definido em <climits>
. Ou pelo menos você pode em teoria, se float
realmente se encaixa no modelo de um float IEEE em vez de algum outro tipo de representação de número real como um Posit / unum.
Não tenho certeza de quanto o padrão ISO C ++ esclarece sobre float
comportamento e se um formato que não fosse baseado em expoentes de largura fixa e campos de significand seria compatível com os padrões.
Nos comentários:
@geza, gostaria de saber o número resultante!
@nada: é 16777216
Você está afirmando que tem este loop para imprimir / devolver 16777216
?
Atualização: como esse comentário foi excluído, acho que não. Provavelmente, o OP está apenas citando o float
antes do primeiro inteiro que não pode ser representado exatamente como 32 bits float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values ou seja, o que eles esperavam verificar com este código bugado.
A versão corrigida do bug, é claro 16777217
, imprimir , o primeiro inteiro que não exatamente representável, em vez do valor anterior.
(Todos os valores flutuantes mais altos são inteiros exatos, mas são múltiplos de 2, depois 4, depois 8, etc. para valores de expoentes maiores que a largura do significante. Muitos valores inteiros maiores podem ser representados, mas 1 unidade no último lugar (do significando) é maior que 1, portanto não são inteiros contíguos. O maior finito float
está logo abaixo de 2 ^ 128, o que é muito grande para mesmo int64_t
.)
Se algum compilador sair do loop original e imprimi-lo, será um bug do compilador.