64位浮点移植问题

我正在将我的应用程序从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位依赖关系,我不知道。

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

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

发布编辑:

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

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

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

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

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

在100个问题中,有99个问题是你在某个地方比较两个浮点数是否相等。

如果问题只是你得到的答案略有不同,你需要意识到两者都不是“正确的” – 无论你在哪种架构上,都会发生某种舍入。 这是一个理解计算中有效数字的问题,并且意识到您提出的任何值都是某种程度的近似值。

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

另请参见: 截断时浮点舍入

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

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

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

除此之外,重要的一点是, 您不应该依赖于跨架构的浮点数学

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 float a=b+c+d; 

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

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

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

 float triangleArea(float a, float b, float c) { long double s = (a+b+c)/2.0; return sqrt((sa)*(sb)*(sc)*c); } 

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

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

  long double s = ((long double)a+b+c)/2.0; 

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