64位浮点移植问题

时间:2009-07-02 19:17:04

标签: c++ c floating-point 64-bit portability

我正在将我的应用程序从32位移植到64位。目前,代码在两种架构下编译,但结果不同。由于各种原因,我使用浮动而不是双打。我假设在一台机器上有一些从浮动到双重的隐式上转换而不是另一台机器。有没有办法控制这个,或者我应该寻找特定的陷阱?

编辑添加:

32位平台

 gcc (GCC) 4.1.2 20070925 (Red Hat 4.1.2-33)
 Dual-Core AMD Opteron(tm) Processor 2218 HE

64位平台

 gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3
 Intel(R) Xeon(R) CPU

应用-mfpmath = 387有点帮助,在算法的1次迭代后,值是相同的,但除此之外,它们再次失去同步。

我还应该补充一点,我担心的不是结果不一样,而是移植到64位平台时发现了我不知道的32位依赖关系。

10 个答案:

答案 0 :(得分:10)

您的编译器可能正在使用SSE操作码在64位平台上执行大部分浮点运算(假设为x86-64),而出于兼容性原因,它可能在之前使用FPU进行大量操作。

SSE操作码提供更多寄存器和一致性(值始终保持32位或64位大小),而FPU尽可能使用80位中间值。因此,您最有可能从这种改进的中间精度中受益。 (注意额外的精度可能导致不一致的结果,如x == y但cos(x)!= cos(y)取决于计算发生的距离!)

您可以尝试使用-mfpmath = 387作为64位版本,因为您正在使用gcc进行编译,并查看您的结果是否与32位结果相匹配,以帮助缩小范围。

答案 1 :(得分:8)

在32位和64位代码之间没有固有的浮点数和双精度表现,但它们经常需要。您的问题的答案将是平台和编译器特定的,因此您需要说明您正在移植的平台以及移植到的平台。

在intel x86平台上,32位代码通常使用x87协处理器指令集和浮点寄存器堆栈以实现最大兼容性,而在amb64 / x86_64平台上,通常使用SSE *指令和xmm *寄存器。它们具有不同的精度特性。

发布编辑:

考虑到您的平台,您可能需要考虑在x86_64版本上尝试-mfpmath = 387(i386 gcc的默认值),看看这是否解释了不同的结果。您可能还需要查看所有-fmath- *编译器开关的设置,以确保它们与两个版本中所需的匹配。

答案 2 :(得分:3)

就像其他人所说的那样,你没有提供足够的信息来准确地说明发生了什么。但从一般意义上讲,似乎你一直指望某种浮点行为,你不应该依赖它。

100次中有99次问题是你在两个地方比较两个浮点数。

如果问题只是你得到的答案略有不同,那么你需要意识到 都不是“正确的” - 无论是什么,某种四舍五入都会发生你正在建设的建筑。这是一个理解计算中有效数字的问题,并且意识到你想出的任何值都是某种程度的近似值。

答案 3 :(得分:3)

x87 FPU的80位内部寄存器导致其浮点结果与内部使用64位的其他FPU略有不同(如x86_64)。你会在这些处理器之间得到不同的结果,除非你不介意通过将内容刷出内存或执行其他“strictfp”技巧来获取大的性能命中率。

另见: Floating point rounding when truncating

和: http://docs.sun.com/source/806-3568/ncg_goldberg.html

答案 4 :(得分:2)

在x64上,使用SSE2指令集,而在32位应用程序中,x87 FPU通常是默认值。

后者在内部以80位格式存储所有浮点值。后者使用普通的32位IEEE浮点数。

除此之外,重点是你不应该依赖于你的浮点数学在各种架构中是相同的

即使您在两台计算机上使用32位版本,仍然无法保证英特尔和AMD会产生相同的结果。当然,当其中一个运行64位构建时,您只会增加更多的不确定性。

依赖于浮点运算的精确结果几乎总是一个错误。

在32位版本上启用SSE2也是一个好的开始,但同样,不要对浮点代码做出假设。总是存在精度损失,并且假设这种损失是可预测的,或者它可以在CPU或不同版本之间重现,这是一个坏主意。

答案 5 :(得分:0)

gnu编译器有许多与浮点数相关的编译器选项,这些选项可能导致计算在某些情况下中断。只需在this page中搜索“浮动”这一术语,您就可以找到它们。

答案 6 :(得分:0)

控制很多这些东西真的很难。

首先,C标准通常要求对“浮动”的操作在“双空格”中完成并转换回浮点数。

英特尔处理器在其用于许多这些操作的寄存器中具有80位精度,然后在存储到主存储器时将其降至64位。这意味着变量的值可能会在没有明显原因的情况下发生变化。

如果您真的关心,可以使用像GnuMP这样的东西,我相信还有其他库可以保证一致的结果。大多数情况下,产生的错误/抖动量低于您所需的真实分辨率。

答案 7 :(得分:0)

真正难以获得的是两个组的结果都是正确的。将变化描述为“不同”之类的东西是不公平的。也许对旧的结果有更多的情感依附......但是没有数学上的理由比64位结果更喜欢32位结果。

您是否考虑过对此应用程序使用定点数学的更改?不仅固定点数学在芯片,编译器和库的变化中保持稳定,在许多情况下它也比浮点数学更快。

作为快速测试,将二进制文件从32位系统移至64位系统并运行它。然后在64位系统上重建应用程序作为32位二进制文​​件,并运行它。这可能有助于确定实际产生不同行为的变化。

答案 8 :(得分:0)

如前所述,只要它们都是正确的,不同的应该不是问题。理想情况下,你应该对这类事情进行单元测试(纯计算通常属于相对容易测试的阵营)。

基本上不可能在CPU和工具链上保证相同的结果(一个编译器标志已经可以改变很多),并且已经非常难以保持一致。设计健壮的浮点代码是一项艰巨的任务,但幸运的是,在许多情况下,精度不是问题。

答案 9 :(得分:0)

需要注意的一件重要事情是C语言最初指定了类似

的计算
float a=b+c+d;

将b,c和d转换为最长的可用浮点类型(恰好是类型double),将它们一起添加,然后将结果转换为float。这样的语义对于编译器来说很简单并且对程序员有帮助,但是有一点困难:存储数字的最有效格式与执行计算的最有效格式不同< / em>的。在没有浮点硬件的机器上,对存储为不一定规范化的64位尾数和单独存储的15位指数和符号的值执行计算会更快,然后对存储为64-的值进行操作位double,必须在每次操作之前解压缩,然后在之后进行标准化和重新打包(即使只是为了下一次操作立即解压缩)。让机器以较长的格式保持中间结果,提高速度和准确性; ANSI C允许使用类型long double

不幸的是,ANSI C未能提供一种方法,通过该方法,变量参数函数可以指示是否要将所有浮点值转换为long double,全部转换为double,或者{ {1}}和float作为doubledouble作为long double传递。如果存在这样的工具,那么编写代码就不容易区分long doubledouble值。遗憾的是,缺少这样的功能意味着在long doubledouble不同类型的系统上,代码必须关注区别,而在不支持区分的系统上则不然。这反过来意味着在类型相同的系统上编写的大量代码将在不存在的系统上中断;编译器供应商认为最简单的解决方法是简单地使long doublelong double同义,并且不提供任何可以准确保存中间计算的类型。

由于以不可表示的类型执行中间计算是不好的,因此有些人认为合乎逻辑的事情是将double上的计算作为类型float执行。虽然有些硬件平台可能比使用float类型更快,但它往往会对准确性产生不良后果。考虑:

double

在使用float triangleArea(float a, float b, float c) { long double s = (a+b+c)/2.0; return sqrt((s-a)*(s-b)*(s-c)*c); } 执行中间计算的系统上,这将产生良好的准确性。在以long double执行中间计算的系统上,即使a,b和c都可以精确表示,这也可能产生可怕的准确性。例如,如果a和b为16777215.0f且c为4.0f,则float的值应为16777217.0,但如果a,b和c的总和计算为s,则它将为是1677216.0;这将产生小于正确值的一半的区域。如果a和c为16777215.0f且b为4.0f(相同数字;不同顺序),那么float将计算为16777218.0,产生 50%太大的区域

如果你的计算在x86上产生了很好的结果(很多编译器急切地推广到80位类型,即使它们无法让程序员无法使用)但是在x64上有糟糕的结果,我猜你可能有一个如上所述的计算需要以比操作数或最终结果更高的精度执行中间步骤。将上述方法的第一行更改为:

s

将强制中间计算以更高的精度完成,而不是以低精度执行计算,然后将不准确的结果存储到更高精度的变量中。