首页 > 解决方案 > 如何在 32 位和 64 位模式下获得相同的双精度操作行为?

问题描述

我有一个要转换为 64 位的库。但是,我无法在 64 位模式下获得精确的结果,因此我的测试失败了。

我将问题简化为一个简单的测试用例:

#include <stdio.h>

int main(void) {
    printf("%d bits: ", sizeof(void*) * 8);
    volatile double d = 10.870191700000001;
    volatile double x = 0.10090000000000002;
    d += x * 30.07;
    printf("%0.15f\n", d);
}

为避免编译器差异,我使用相同的编译器和交叉编译。在这种情况下,我在 Core i5 CPU 中的 Windows 7 上使用 TDM-GCC 64 位 5.1.0。这是我的命令行:

gcc double_test.c -o double_test.exe -m32 -O0 && double_test.exe && gcc double_test.c -o double_test.exe -m64 -O0 && double_test.exe

输出是:

32 bits: 13.904254700000001
64 bits: 13.904254700000003

在这种情况下,错误很小,但在我的完整测试用例中,错误可以加起来足以使我的输出翻倍。

如何获得位精确运算以匹配 32 位输出?

我最接近相关的东西是使用-ffloat-store,但在这个片段中,它得到了 32 位执行,就像 64 位一样,而我需要的正好相反。但是,这对我的图书馆没有任何明显的影响。我也测试了-fexcess-precision=standardand-mfp-math选项无济于事。

标签: cgccx86floating-pointdouble

解决方案


既然您说您需要更精确的...01结果以及确定性,那么不幸的是,您不能只-msse2 -mfpmath=sse在 32 位构建中使用。寻找确定性的未来读者应该使用它。


您可以使用-mfpmath=387要求 gcc 在 64 位模式下使用慢速/过时的 x87 数学,这不是默认设置。调用约定在 xmm 寄存器中传递/返回 FP 参数,因此这比 32 位模式更糟糕,有时需要额外的存储/重新加载。

peter@volta:/tmp$ gcc -m64 -mfpmath=387 -O3 fp-prec.c -o fp-64-387
peter@volta:/tmp$ ./fp-64-387 
64 bits: 13.904254700000001

我不确定当自动矢量化成为可能时 gcc 是否严格限制在 x87 上。如果是这样,你就错过了性能。


顺便说一句,在您的示例中,这...01在将其添加到. (是,但仍然等价于所以不会先四舍五入到 64 位)。x*30.07ddvolatiled += stuffd = d + stuffx*30.07double

您可以使用long double, 例如d += x * (long double)30.07在此处强制使用 80 位临时文件。 long double在 x86-64 System V ABI Linux/OS X/*BSD/etc 中是 80 位,但在 x64 Windows 上它与 64-bit 相同double。所以这可能不是你的选择。


在这种情况下,您可以使用 FMA 获得相同的结果,该 FMA 在进行加法之前保持乘法的无限精度。这在没有 FMA 支持的硬件上很慢,但fma(d, 30.07, x)会可靠地给出您想要的结果。

如果您需要它,请在需要该精度的地方使用它。

如果您在启用 FMA 的情况下进行编译,它可以内联到 FMA 指令。(例如-march=native在我的 Skylake CPU 上)

即使不使用fma()math.h 函数,gcc 在优化时也会将 mul+add 表达式压缩到 FMA 中。(与 Clang 不同,我认为FP_CONTRACT默认情况下没有-ffast-math. 请注意,我没有使用-march=387

 # your original source code, using an FMA instruction (native=skylake in my case)
peter@volta:/tmp$ gcc -m64 -march=native -O3 fp-prec.c -o fp-64-native
peter@volta:/tmp$ ./fp-64-native 
64 bits: 13.904254700000001

相关部分main是:

 57e:   c5 fb 10 44 24 08       vmovsd xmm0,QWORD PTR [rsp+0x8] # load x
 584:   c5 fb 10 0c 24          vmovsd xmm1,QWORD PTR [rsp]     # load d
 589:   c4 e2 f1 99 05 d6 01 00 00      vfmadd132sd xmm0,xmm1,QWORD PTR [rip+0x1d6]        # the 30.07 constant
 592:   c5 fb 11 04 24          vmovsd QWORD PTR [rsp],xmm0     # store d
 597:   c5 fb 10 04 24          vmovsd xmm0,QWORD PTR [rsp]     # reload d
 59c:   e8 8f ff ff ff          call   530 <printf@plt>

FP确定性通常很难

另请参阅https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/https://randomascii.wordpress.com/2012/03/21/intermediate-floating-point-precision/


推荐阅读