fp:fast 3000.f/1000.f != 3.f 可以吗?

Is it ok that with fp:fast 3000.f/1000.f != 3.f?

我正在使用 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);
}

失败结果:

error: in "test_division": check (v / 1000.f) == 3.f has failed [3.00000024 != 3]

我知道 /fp:fast 在某些情况下可能会有更差的浮点精度;但是,在这里,似乎过分了...

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

我知道 0.1 + 0.2 不是 0.3,但这里所有的数字都是可表示的,除法 returns 恰好是 3 fp:precise.

是否有可能我翻转了一些其他标志,从而进一步降低了浮点精度?

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 值。