gez*_*eza 88 c++ floating-point clang
我试图检查哪里float失去了精确表示大整数的能力。所以我写了这个小片段:
int main() {
for (int i=0; ; i++) {
if ((float)i!=i) {
return i;
}
}
}
Run Code Online (Sandbox Code Playgroud)
该代码似乎适用于所有编译器,但不包括clang。Clang生成一个简单的无限循环。上帝保佑。
可以吗?如果是,那是QoI问题吗?
Rei*_*ica 63
请注意,内置运算符!=要求其操作数具有相同的类型,并在必要时使用提升和转换来实现。换句话说,您的情况等同于:
(float)i != (float)i
Run Code Online (Sandbox Code Playgroud)
那将永远不会失败,因此代码最终将溢出i,为您的程序提供未定义的行为。因此,任何行为都是可能的。
要正确检查您要检查的内容,您应该将结果投射回int:
if ((int)(float)i != i)
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 47
正如@Angew指出的那样,!=运算符在两侧都需要相同的类型。
(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是false,NAN != NAN是true)。
gcc7.4及更早版本正确地将代码优化jnp为循环分支(https://godbolt.org/z/fyOhW1):只要操作数x != x 不是NaN ,就保持循环。(gcc8及更高版本还会检查是否je中断循环,无法基于对任何非NaN输入始终为真的事实进行优化)。x86 FP比较无序设置的PF。
而且,顺便说一句,这意味着clang的优化也是安全的:CSE必须与CSE (float)i != (implicit conversion to float)i相同,并且证明i -> float在可能的范围内绝不是NaN int。
(尽管考虑到此循环将遇到有符号溢出的UB,但ud2无论它的实际循环体是什么,都可以发出它想要的任何asm,包括非法指令或空的无限循环。)但是忽略有符号溢出的UB。 ,此优化仍然是100%合法的。
GCC无法优化循环体,甚至-fwrapv无法使有符号整数溢出得到明确定义(作为2的补码环绕)。 https://godbolt.org/z/t9A8t_
即使启用-fno-trapping-math也无济于事。(不幸的是,
-ftrapping-math即使GCC的实现是损坏的, GCC的默认值也是启用它。)int-> float转换可能会导致FP不精确的异常(对于太大而无法准确表示的数字),因此有可能不加掩饰,这是合理的优化环体。(因为16777217如果不精确的异常未被屏蔽,则转换为浮点型可能会有明显的副作用。)
但是,使用时-O3 -fwrapv -fno-trapping-math,有100%的优化未将其编译为空的无限循环。如果不使用#pragma STDC FENV_ACCESS ON,则记录被屏蔽的FP异常的粘性标志的状态不是该代码可观察到的副作用。否int-> float转换会导致NaN,所以x != x不正确。
这些编译器都针对使用IEEE 754单精度(binary32)float和32位的C ++实现进行了优化int。
该bugfixed(int)(float)i != i循环将有UB对C ++实现窄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://zh.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values, 即他们希望通过此错误代码进行验证的内容。
错误修复的版本当然会打印出16777217,第一个不能精确表示的整数,而不是之前的值。
(所有较高的float值都是精确的整数,但是对于大于有效宽度的指数值,它们是2,4,8的倍数。可以表示许多较高的整数值,但最后一个单位(有效数的)大于1,所以它们不是连续的整数。最大的有限float值刚好在2 ^ 128以下,对于偶数而言太大int64_t。)
如果有任何编译器确实退出了原始循环并打印出来,那将是编译器错误。
| 归档时间: |
|
| 查看次数: |
3236 次 |
| 最近记录: |