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.0
produz 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.0
nã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 x
ele 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 == x
sempre.
- é
+/- infinity
, então o resultado é +/- infinity
do 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.0
sã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 0
com 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 == x
mesmo quando x
é um zero (negativo).
O caso da subtração
No modo de arredondamento padrão , a subtração x-0.0
també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 é+/- infinity
do mesmo sinal.
- é um número (sub) normal,
x-0.0 == x
sempre.
- é
-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.0
como resultado de (-0.0) + (-0.0)
, porque -0.0
difere no sinal de nenhum dos adendos, enquanto +0.0
difere 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.0
uma no-op e x == x-0.0
uma 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.0
e x*1.0
para finito x
tem 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 -> x
se 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-math
pode 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 x
todos, de x
acordo 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
x
quando é 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-math
precisa ser passado para Clang ou GCC.