使用fp:fast,3000.f/1000.f不等于3.f,这是否合适?

5

我正在使用 MSVC 2019 v16.11.12。

当我尝试使用 /fp:fast 而不是 /fp:precise 编译我的代码时,我的测试开始失败。

最简单的情况是:

BOOST_AUTO_TEST_CASE(test_division) {
    float v = 3000.f;
    BOOST_TEST((v / 1000.f) == 3.f);
}

执行结果出错:

错误: 在"test_division"中,检查 (v / 1000.f) == 3.f 时失败了 [3.00000024 != 3]

我知道/fp:fast在某些情况下可能会导致浮点数精度更差;但是,在这种情况下,看起来过于夸张...

为什么它不能准确地将3000除以1000?

我知道0.1 + 0.2不等于0.3,但是这里所有数字都是可表示的,而且使用fp:precise后该除法返回的确切值为3。

有可能我开启了其他标志,进一步降低了浮点数精度吗?


1
你不能用浮点数准确地表示0.1、0.2或0.3。然而,在这里,这些数字可以被准确表示。使用fp:precise,最终结果符合预期。 - user1782685
1
各位,这不是典型的fp精度问题。这些整数在fp中可以准确表示。无论是gcc还是clang都会将其优化为始终为真的assert。考虑到这些是常量值,这似乎是一个MSVC的bug或者至少是毫无意义的不精确。 - GManNickG
10
使用/fp:fast编译选项,优化器可能会将"/ 1000.f"转换为"* .001f",即乘以其倒数,因为乘法要比除法快。但是,浮点数中无法准确地表示0.001,因此会产生舍入误差。 - Chris Dodd
2
@drescherjm 在编译器浏览器上无法重现,但是当我在本地的VS-2019上使用fp:fast构建时,这段代码最后一个结果的位模式与0x40400000不同,为0x40400001 - Adrian Mole
2
@AdrianMole 我可以重现它 https://godbolt.org/z/bY1oeos7q - phuclv
显示剩余4条评论
1个回答

5

gcc 的 -ffast-math 选项(以及可能是 msvc 的 /fp:fast 选项)启用的优化之一是将“常数除法”转换为“倒数乘法”,因为浮点数除法很慢——在某些机器上比乘法昂贵多达10倍以上,因为乘法器通常被管道化,而除法器则不太常被管道化。

使用这种方法,/ 1000.f 将被转换为某个精度的 * .001,而 .001 无法在浮点数中被准确表示,因此会产生一些不精确性。

更精确地说,最接近 .001 的32位FP值为0x1.0624dep-10,而最接近64位FP值为0x1.0624dd2f1a9fcp-10。如果将该32位值乘以3000,则得到0x1.80000132p+1,约为3.0000001425。如果将其舍入为32位,则得到0x1.800002p+1,约为3.0000002384。有趣的是,如果使用64位值并将其乘以3000,则得到0x1.800000000000024p+1,将其舍入为64位后即为精确的0x1.8p+1值。


是的。将 v / 1000.f 更改为 v * 0.001f 会导致测试失败(在我的本地 VS-2019 上),即使使用了 fp:precise - Adrian Mole
1
实际上,MSVC 生成的是 mulss/fmul 而不是 divss/fdiv:https://godbolt.org/z/bY1oeos7q - phuclv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接