As @Angew pointed out,!= 运算符需要两边的类型相同。
(float)i != i 导致 RHS 也浮起来,所以我们有 (float)i != (float)i。
g++ 也会生成一个无限循环,但它不会优化其中的工作。您可以看到它将 int->float 转换为 cvtsi2ss 并执行 ucomiss xmm0,xmm0 以将 (float)i 与自身进行比较。 (这是您的第一个线索,即您的 C++ 源代码并不像 @Angew 的回答所解释的那样意味着您认为它所做的事情。)
x != x 仅在“无序”时为真,因为x 是 NaN。 (INFINITY 在 IEEE 数学中与自身比较,但 NaN 不相等。NAN == NAN 为假,NAN != NAN 为真)。
gcc7.4 和更早的版本正确地将您的代码优化为 jnp 作为循环分支 (https://godbolt.org/z/fyOhW1):只要 x != x 的操作数不是 NaN,就保持循环。 (gcc8 及更高版本还检查 je 以跳出循环,基于任何非 NaN 输入始终为真这一事实而未能优化)。 x86 FP 在无序上比较集合 PF。
顺便说一句,这意味着 clang 的优化也是安全的:它只需要 CSE (float)i != (implicit conversion to float)i 相同,并证明 i -> float 在 @987654345 的可能范围内绝不是 NaN @。
(尽管这个循环会遇到有符号溢出 UB,但它实际上可以发出任何它想要的 asm,包括 ud2 非法指令,或者一个空的无限循环,不管循环体实际上是什么。)但是忽略签名溢出 UB,这种优化仍然是 100% 合法的。
GCC 未能优化掉循环体即使使用-fwrapv 来明确定义有符号整数溢出(作为 2 的补码环绕)。 https://godbolt.org/z/t9A8t_
即使启用-fno-trapping-math 也无济于事。 (GCC 的默认值是 unfortunately 以启用
-ftrapping-math,即使 GCC's implementation of it is broken/buggy。)int->float 转换可能导致 FP 不精确异常(对于数字太大而无法准确表示),因此可能会出现未屏蔽的异常合理地不优化循环体。 (因为如果未屏蔽不精确的异常,将 16777217 转换为 float 可能会产生可观察到的副作用。)
但是对于-O3 -fwrapv -fno-trapping-math,不将其编译为空的无限循环是 100% 错过的优化。如果没有#pragma STDC FENV_ACCESS ON,记录被屏蔽的 FP 异常的粘性标志的状态不是代码的可观察到的副作用。没有int->float 转换会导致 NaN,所以x != x 不可能为真。
这些编译器都针对使用 IEEE 754 单精度 (binary32) float 和 32 位 int 的 C++ 实现进行了优化。
bugfixed (int)(float)i != i 循环将在具有窄 16 位 int 和/或更宽 float 的 C++ 实现上具有 UB,因为在到达之前您会遇到有符号整数溢出 UB第一个不能完全表示为 float 的整数。
但是,在使用 x86-64 System V ABI 为 gcc 或 clang 等实现进行编译时,在一组不同的实现定义选择下的 UB 不会产生任何负面影响。
顺便说一句,您可以根据<climits> 中定义的FLT_RADIX 和FLT_MANT_DIG 静态计算此循环的结果。或者至少理论上你可以,如果 float 实际上适合 IEEE 浮点模型,而不是像 Posit / unum 之类的其他实数表示。
我不确定 ISO C++ 标准对float 行为的规定有多少,以及不基于固定宽度指数和有效数字字段的格式是否符合标准。
在 cmets 中:
@geza 我很想知道结果数字!
@nada:是 16777216
你是否声称你有这个循环来打印/返回16777216?
更新:由于该评论已被删除,我认为不会。可能 OP 只是在第一个不能精确表示为 32 位 float 的整数之前引用 float。 https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values 即他们希望用这个有缺陷的代码来验证什么。
修正错误的版本当然会打印16777217,第一个不完全可表示的整数,而不是之前的值。
(所有更高的浮点值都是精确整数,但它们是 2 的倍数,然后是 4,然后是 8,等等。对于高于有效数字宽度的指数值。可以表示许多更高的整数值,但 1 个单位最后一位(有效数)大于 1,因此它们不是连续整数。最大的有限 float 刚好低于 2^128,这对于 int64_t 来说也太大了。)
如果任何编译器确实退出了原始循环并打印出来,那将是编译器错误。