是否可以进行浮点优化?

时间:2019-07-12 09:15:42

标签: c++ floating-point clang

我试图检查float在哪里失去了精确表示大整数的能力。所以我写了这个小片段:

int main() {
    for (int i=0; ; i++) {
        if ((float)i!=i) {
            return i;
        }
    }
}

此代码似乎适用于所有编译器(clang除外)。 Clang生成一个简单的无限循环。 Godbolt

可以吗?如果是,那是QoI问题吗?

2 个答案:

答案 0 :(得分:61)

请注意,内置运算符!=要求其操作数为同一类型,并在必要时使用提升和转换来实现。换句话说,您的情况等同于:

(float)i != (float)i

那应该永远不会失败,因此代码最终将溢出i,为您的程序提供未定义的行为。因此,任何行为都是可能的。

要正确检查要检查的内容,应将结果回退到int

if ((int)(float)i != i)

答案 1 :(得分:39)

As @Angew pointed out!=运算符两边都需要相同的类型。  (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绝不可能是NaN范围int

(尽管该循环将遇到有符号溢出的UB,但实际上它可以发出它想要的任何asm,包括ud2非法指令,或者是一个空的无限循环,无论循环主体是什么。 ),但是忽略签名溢出的UB,此优化仍然是100%合法的。


即使使用-fwrapv,GCC也无法优化循环体,无法使有符号整数溢出得到明确定义(作为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 ++实现进行了优化。

修正了(int)(float)i != i 的循环会在窄16位int和/或更宽float的C ++实现中使用UB,因为您遇到了签名-integer溢出UB,然后到达第一个不能完全表示为float的整数。

但是,使用x86-64 System V ABI编译gcc或clang之类的实现时,UB在不同的实现定义的选择集下不会产生任何负面影响。


顺便说一句,您可以根据<climits>中定义的FLT_RADIXFLT_MANT_DIG静态计算此循环的结果。或者至少在理论上您可以做到,如果float实际上适合IEEE浮点模型,而不是像Posit / unum这样的其他实数表示形式。

我不确定ISO C ++标准对float的行为有何规定,以及不确定不是基于固定宽度指数和有效字段的格式是否符合标准。


在评论中:

  
    

@geza我想听听得到的电话号码!

  
     

@nada:是16777216

您是否声称要让此循环打印/返回16777216

更新:由于该评论已被删除,我认为没有。可能OP只是在无法完全表示为32位float的第一个整数之前引用floathttps://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values,即他们希望通过此错误代码验证的内容。

错误修复的版本当然会打印16777217,它是不能可精确表示的第一个整数,而不是之前的值。

(所有较高的float值都是精确整数,但是对于大于有效宽度的指数值,它们是2,4,8等的倍数。可以表示许多较高的整数值,但是1个单位为(有效位的)最后一位大于1,因此它们不是连续的整数。最大的有限float刚好在2 ^ 128以下,对于int64_t来说太大了。)

如果有任何编译器确实退出了原始循环并打印出来,那将是编译器错误。