IEEE 754-2008浮点运算标准和ISO/IEC 10967语言无关算术(LIA)标准,第1部分解释了为什么会这样。
IEEE 754 § 6.3 符号位
当输入或结果为 NaN 时,该标准不解释 NaN 的符号。但是,请注意,对于位串的操作(复制、取反、绝对值、copySign),会指定 NaN 结果的符号位,有时基于 NaN 操作数的符号位。逻辑谓词 totalOrder 也受 NaN 操作数的符号位影响。对于所有其他操作,即使只有一个输入 NaN 或者 NaN 是由无效操作产生的,该标准也不指定 NaN 结果的符号位。
当输入和结果都不是 NaN 时,乘积或商的符号是操作数符号的异或;和或差 x−y 视为和 x+(−y) 的符号与加数的符号至多不同;转换、量化操作、roundTo-Integral 操作和 roundToIntegralExact(参见 5.3.1)的结果的符号是第一个或唯一操作数的符号。即使操作数或结果为零或无穷大,这些规则也适用。
当两个带相反符号的操作数之和(或相似符号的两个操作数之差)恰好为零时,在除 roundTowardNegative 外的所有舍入方向属性中,该和(或差)的符号应为 +0;在该属性下,精确零和(或差)的符号应为 −0。但是,x+x=x−(−x) 即使 x 为零,仍保持与 x 相同的符号。
加法的情况
在默认的舍入模式下 (四舍五入,遇到平局向偶数靠近),我们发现x+0.0
产生x
,除非x
是-0.0
:在这种情况下,我们有两个相反符号的操作数之和为零,§6.3第3段规定此加法产生+0.0
。
由于+0.0
与原始的-0.0
不是按位相同的,并且-0.0
是可能作为输入出现的合法值,编译器有义务放入代码,将潜在的负零转换为+0.0
。
总结:在默认的舍入模式下,在x+0.0
中,如果x
- 不是
-0.0
,那么x
本身是一个可接受的输出值。
- 是
-0.0
,那么输出值必须是+0.0
,这与-0.0
不是按位相同的。
乘法情况
在默认舍入模式下,使用x*1.0
就不会出现这种问题。如果x
:
减法的情况
在默认的舍入模式下,减法 x-0.0
也是一个无操作,因为它等同于 x + (-0.0)
。如果 x
是
- 如果是
NaN
,则 §6.3p1 和 §6.2.3 适用于加法和乘法。
- 如果是
+/- infinity
,则结果为相同符号的+/- infinity
。
- 如果是(次)规范数,则
x-0.0 == x
总是成立。
- 如果是
-0.0
,则根据 §6.3p2 我们有“[...]将差x - y视为和x + (-y)的符号与至多一个加数的符号不同;”。这迫使我们将(-0.0) + (-0.0)
的结果赋值为-0.0
,因为-0.0
的符号与任何一个加数都不同,而+0.0
与两个加数的符号不同,违反了此条款。
- 如果是
+0.0
,则这归结为上面在加法情况下的案例中考虑过的加法情况(+0.0) + (-0.0)
,根据§6.3p3被判定为给出+0.0
。
由于对于所有情况,输入值都合法作为输出值,因此可以将 x-0.0
视为无操作,并将 x == x-0.0
视为重言式。
值更改优化
IEEE 754-2008标准有以下有趣的引用:
IEEE 754 § 10.4 字面意义和值更改优化
[...]
以下是保留源代码字面意义的一些值更改转换:
- 当 x 不为零且不是信号 NaN 且结果具有与 x 相同的指数时,应用恒等属性 0 + x。
- 当 x 不是信号 NaN 且结果具有与 x 相同的指数时,应用恒等属性 1 × x。
- 更改安静 NaN 的有效负载或符号位。
- [...]
由于所有 NaN 和所有无穷大共享相同的指数,并且对于有限的 x
,x+0.0
和 x*1.0
的正确舍入结果具有与 x
完全相同的大小,因此它们的指数相同。
sNaNs
信号NaN是浮点陷阱值;它们是特殊的NaN值,如果将其用作浮点操作数,则会导致无效操作异常(SIGFPE)。如果优化掉触发异常的循环,软件将不再具有相同的行为。
然而,正如user2357112在评论中指出的那样,C11标准明确地未定义了信号NaN(sNaN
)的行为,因此编译器可以假设它们不会发生,从而也不会引发它们所引发的异常。C++11标准省略了对信号NaN的行为描述,因此也将其留给未定义的状态。
舍入模式
在替代舍入模式下,允许的优化可能会改变。例如,在向负无穷舍入模式下,优化x+0.0 -> x
变得可行,但x-0.0 -> x
变得被禁止。
为了防止GCC假定默认的舍入模式和行为,可以将实验性标志
-frounding-math
传递给GCC。
结论
即使在
-O3
的情况下,Clang和
GCC仍然符合IEEE-754标准。这意味着它必须遵守IEEE-754标准的上述规则。
x+0.0
对于所有
x
都不是位相同的,但
x*1.0
可能会被选择为如此:即当我们
1. 遵守建议,在
x
是NaN时保持有效负载不变。
2. 通过
* 1.0
保持NaN结果的符号位不变。
3. 在商/积中,当
x
不是NaN时,遵守异或符号位的顺序。
要启用 IEEE-754 不安全优化
(x+0.0) -> x
,需要将标志
-ffast-math
传递给 Clang 或 GCC。