[英]Is this floating-point optimization allowed?
我试图检查float
失去了准确表示大整数的能力。 所以我写了这个小片段:
int main() {
for (int i=0; ; i++) {
if ((float)i!=i) {
return i;
}
}
}
这段代码似乎适用于除clang之外的所有编译器。 Clang生成一个简单的无限循环。 Godbolt 。
这是允许的吗? 如果是,那是否是QoI问题?
请注意,内置运算符!=
要求其操作数具有相同的类型,并且如果需要,将使用促销和转换实现该操作数。 换句话说,您的条件相当于:
(float)i != (float)i
这应该永远不会失败,所以代码最终会溢出i
,给你的程序Undefined Behavior。 因此任何行为都是可能的。
要正确检查要检查的内容,应将结果转换回int
:
if ((int)(float)i != i)
正如@Angew指出的那样 , !=
运算符在两侧都需要相同的类型。 (float)i != i
导致RHS的推广也浮动,所以我们有(float)i != (float)i
。
g ++也会生成一个无限循环,但它并没有优化它内部的工作。 你可以看到它用cvtsi2ss
转换int-> float并且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
对于int
的可能范围永远不会是NaN 。
(虽然假设这个循环会遇到有符号溢出的UB,但它允许按字面意思发出它想要的任何asm,包括一个ud2
非法指令,或者一个空的无限循环,无论循环体实际上是什么。)但忽略了有符号溢出UB,这种优化仍然100%合法。
即使使用-fwrapv
, GCC也无法优化掉循环体,以使有符号整数溢出定义明确 (作为2的补码环绕)。 https://godbolt.org/z/t9A8t_
即使启用-fno-trapping-math
也无济于事。 ( 不幸的是,GCC的默认设置启用了
-ftrapping-math
即使GCC的实现被破坏/错误 。)int-> float转换可能导致FP不精确的异常(对于数字太大而无法准确表示),因此,如果异常可能未被掩盖,则不合理地优化掉循环体。 (因为如果不屏蔽异常, 16777217
转换为float可能会产生可观察到的副作用。)
但是使用-O3 -fwrapv -fno-trapping-math
,它100%错过了优化,而不是将其编译为空的无限循环。 如果没有#pragma STDC FENV_ACCESS ON
,则记录屏蔽FP异常的粘性标记的状态不是代码的可观察副作用。 没有int
- > float
转换会导致NaN,所以x != x
不能为true。
这些编译器都针对使用IEEE 754单精度(binary32) float
和32位int
C ++实现进行了优化。
bugfixed (int)(float)i != i
循环在C ++实现上具有UB,具有窄的16位int
和/或更宽的float
,因为在到达第一个不是第一个整数之前你会遇到有符号整数溢出UB完全可以表示为float
。
但是,在使用x86-64 System V ABI编译gcc或clang等实现时,UB在一组不同的实现定义选择下没有任何负面影响。
顺便说一句,您可以静态计算此循环的结果来自FLT_RADIX
和FLT_MANT_DIG
,在<climits>
定义。 或者至少你可以在理论上,如果float
实际上适合IEEE浮点数的模型,而不是像Posit / unum那样的其他类型的实数表示。
我不确定ISO C ++标准对float
行为的重视程度以及不基于固定宽度指数和有效数字字段的格式是否符合标准。
在评论中:
@geza我很想听到结果号码!
@nada:这是16777216
你声称你有这个循环打印/返回16777216
?
更新:由于该评论已删除,我认为不是。 可能OP只是在第一个整数之前引用float
,它不能完全表示为32位float
。 https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values即他们希望用这个错误代码验证的内容。
bugfixed版本当然会打印16777217
,这是第一个不完全可表示的整数,而不是之前的值。
(所有较高的浮点值都是精确整数,但是对于高于有效数字宽度的指数值,它们是2的倍数,然后是4,然后是8等。可以表示许多更高的整数值,但最后一个位置是1个单位(有效数字)大于1所以它们不是连续的整数。最大的有限float
低于2 ^ 128,这对于偶数int64_t
来说太大了。)
如果任何编译器确实退出原始循环并打印它,那将是编译器错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.