A norma IEEE 754-2008 para aritmética de ponto flutuante e a norma aritmética independente de idioma (LIA) ISO / IEC 10967, parte 1, respondem por que isso é assim.
IEEE 754 § 6.3 O bit de sinal
Quando uma entrada ou resultado é NaN, esse padrão não interpreta o sinal de um NaN. Observe, no entanto, que as operações nas cadeias de bits - copiar, negar, abs, copySign - especificam o bit de sinal de um resultado NaN, às vezes com base no bit de sinal de um operando NaN. O predicado lógico totalOrder também é afetado pelo bit de sinal de um operando NaN. Para todas as outras operações, este padrão não especifica o bit de sinal de um resultado NaN, mesmo quando há apenas uma entrada NaN ou quando o NaN é produzido a partir de uma operação inválida.
Quando nem as entradas nem o resultado são NaN, o sinal de um produto ou quociente é o OR exclusivo dos sinais dos operandos; o sinal de uma soma ou de uma diferença x - y considerada uma soma x + (−y) difere de no máximo um dos sinais dos adendos; e o sinal do resultado das conversões, a operação de quantização, as operações roundTo-Integral e roundToIntegralExact (consulte 5.3.1) é o sinal do primeiro ou único operando. Essas regras serão aplicadas mesmo quando operandos ou resultados forem zero ou infinitos.
Quando a soma de dois operandos com sinais opostos (ou a diferença de dois operandos com sinais semelhantes) é exatamente zero, o sinal dessa soma (ou diferença) deve ser +0 em todos os atributos de direção de arredondamento, exceto roundTowardNegative; sob esse atributo, o sinal de uma soma zero exata (ou diferença) deve ser -0. No entanto, x + x = x - (−x) mantém o mesmo sinal que x, mesmo quando x é zero.
O caso da adição
Sob o modo de arredondamento padrão (Round-a-Nearest, Ties-a-Even) , vemos que x+0.0produz x, exceto quando xé -0.0: Nesse caso, temos uma soma de dois operandos com sinais opostos cuja soma é zero, e §6.3 parágrafo 3 regras que essa adição produz +0.0.
Como +0.0não é bit a bit idêntico ao original -0.0, e esse -0.0é um valor legítimo que pode ocorrer como entrada, o compilador é obrigado a inserir o código que transformará potenciais zeros negativos em +0.0.
O resumo: No modo de arredondamento padrão, em x+0.0, sex
- não é
-0.0 , então xele próprio é um valor de saída aceitável.
- é
-0.0 , então o valor de saída deve ser +0.0 , que não é bit a bit idêntico a -0.0.
O caso da multiplicação
No modo de arredondamento padrão , esse problema não ocorre com x*1.0. Se x:
- é um número (sub) normal,
x*1.0 == xsempre.
- é
+/- infinity, então o resultado é +/- infinitydo mesmo sinal.
é NaN, então de acordo com
IEEE 754 § 6.2.3 Propagação de NaN
Uma operação que propaga um operando NaN para seu resultado e possui um único NaN como entrada deve produzir um NaN com a carga útil da entrada NaN, se representável no formato de destino.
o que significa que o expoente e a mantissa (embora não seja o sinal) de NaN*1.0são recomendados para permanecer inalterado em relação à entrada NaN. O sinal não é especificado de acordo com §6.3p1 acima, mas uma implementação pode especificar que seja idêntico à fonte NaN.
- é
+/- 0.0, então o resultado é um 0com seu bit de sinal XORed com o bit de sinal de 1.0, de acordo com §6.3p2. Como o bit de sinal de 1.0é 0, o valor de saída é inalterado em relação à entrada. Assim, x*1.0 == xmesmo quando xé um zero (negativo).
O caso da subtração
No modo de arredondamento padrão , a subtração x-0.0também é não operacional, porque é equivalente a x + (-0.0). Se xé
- é
NaN , então, §6.3p1 e §6.2.3 se aplicam da mesma maneira que para adição e multiplicação.
- é
+/- infinity, então o resultado é+/- infinitydo mesmo sinal.
- é um número (sub) normal,
x-0.0 == xsempre.
- é
-0.0, então, em §6.3p2, temos " [...] o sinal de uma soma, ou de uma diferença x - y considerada uma soma x + (−y), difere de no máximo um dos sinais dos adendos; " Isso nos obriga a atribuir -0.0como resultado de (-0.0) + (-0.0), porque -0.0difere no sinal de nenhum dos adendos, enquanto +0.0difere no sinal de dois dos adendos, violando esta cláusula.
- é
+0.0, então, isso se reduz ao caso de adição (+0.0) + (-0.0)considerado acima em The Case of Addition , que por §6.3p3 está decidido a dar +0.0.
Como em todos os casos o valor de entrada é legal como saída, é permitido considerar x-0.0uma no-op e x == x-0.0uma tautologia.
Otimizações de mudança de valor
A norma IEEE 754-2008 possui a seguinte citação interessante:
IEEE 754 § 10.4 Significado literal e otimizações de mudança de valor
[...]
As seguintes transformações de alteração de valor, entre outras, preservam o significado literal do código-fonte:
- A aplicação da propriedade de identidade 0 + x quando x não é zero e não é um NaN de sinalização e o resultado tem o mesmo expoente que x.
- A aplicação da propriedade de identidade 1 × x quando x não é um NaN de sinalização e o resultado tem o mesmo expoente que x.
- Alterando a carga ou sinal de um NaN silencioso.
- [...]
Como todos os NaNs e todos os infinitos compartilham o mesmo expoente, e o resultado arredondado corretamente de x+0.0e x*1.0para finito xtem exatamente a mesma magnitude que x, seu expoente é o mesmo.
sNaNs
NaNs de sinalização são valores de interceptação de ponto flutuante; São valores especiais de NaN cujo uso como um operando de ponto flutuante resulta em uma exceção de operação inválida (SIGFPE). Se um loop que desencadeia uma exceção fosse otimizado, o software não se comportaria mais da mesma maneira.
No entanto, como o usuário2357112 aponta nos comentários , o Padrão C11 deixa explicitamente indefinido o comportamento dos NaNs de sinalização (sNaN ), de modo que o compilador pode assumir que eles não ocorrem e, portanto, as exceções que eles geram também não ocorrem. O padrão C ++ 11 omite a descrição de um comportamento para sinalizar NaNs e, portanto, também o deixa indefinido.
Modos de arredondamento
Nos modos alternativos de arredondamento, as otimizações permitidas podem mudar. Por exemplo, no modo Arredondar para Infinito Negativo , a otimização x+0.0 -> xse torna permitida, masx-0.0 -> x é proibida.
Para impedir que o GCC assuma os modos e comportamentos padrão de arredondamento, o sinalizador experimental -frounding-mathpode ser passado para o GCC.
Conclusão
Clang e GCC , mesmo em -O3, permanecem em conformidade com a IEEE-754. Isso significa que ele deve seguir as regras acima do padrão IEEE-754. nãox+0.0 é nem um pouco idêntico a xtodos, de xacordo com essas regras, mas x*1.0 pode ser escolhido assim : ou seja, quando
- Obedeça à recomendação de passar a carga útil inalterada de
xquando é um NaN.
- Deixe o bit de sinal de um resultado NaN inalterado por
* 1.0.
- Obedeça à ordem de XOR o bit de sinal durante um quociente / produto, quando não
x for um NaN.
Para ativar a otimização não segura IEEE-754 (x+0.0) -> x, o sinalizador -ffast-mathprecisa ser passado para Clang ou GCC.