c - 如何在 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=standard
and-mfp-math
选项无济于事。
解决方案
既然您说您需要更精确的...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.07
d
d
volatile
d += stuff
d = d + stuff
x*30.07
double
您可以使用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/
推荐阅读
- reactjs - 使用 reactt 测试库为复选框编写单元测试用例
- javascript - db 和我的本地服务器之间的 imageUrl 区别
- angular - Angular Apollo writeQuery 更改了缓存,但 UI(订阅变量)没有
- powerbi - 展开幂查询中的所有列(列数可变)
- python - Jupyter Notebbok 中的日期时间错误不匹配
- c# - 在 Repository 模式中,Repository 必须是 Collection Like 接口吗?
- javascript - 用于预订系统的 JavaScript 日期选择器
- flutter - 无效参数:http 包中的 URI 中未指定主机 (Flutter)
- c# - 从 AWS Lambda C# 访问 tmp 文件夹中的 csv 文件
- python - 如何找到具有离散数据点的部分闭合形状的区域