C# - 32位和64位上的数学运算结果不一致

11

考虑以下代码:

double v1 = double.MaxValue;
double r = Math.Sqrt(v1 * v1);

在32位机器上,r = double.MaxValue 在64位机器上,r = Infinity。 我们在32位的机器上开发,直到客户通知我们才意识到这个问题。为什么会出现这种不一致性?如何避免这种情况发生?


2
在这里,无论是32位还是64位机器,都会产生+inf的结果。 - Joey
5个回答

22
x86指令集由于FPU的工作方式具有棘手的浮点一致性问题。内部计算使用比double存储的更多有效位,当数字从FPU堆栈刷新到内存时会导致截断。
x64 JIT编译器已经解决了这个问题,它使用SSE指令,SSE寄存器与double相同大小。
当您的计算测试浮点精度和范围的边界时,这可能会影响您。您永远不希望接近需要超过15个有效数字,永远不希望接近10E308或10E-308。您肯定不想对最大表示值进行平方。这通常不是真正的问题,代表物理量的数字不会接近这些极限。
利用这个机会找出计算中的问题很重要。您应该运行与客户使用的操作系统和硬件相同的环境。在只测试x86机器上的代码之后再发布是不可取的。
快速而简单的修复方法是:项目属性 -> 编译选项卡 -> 平台目标 = x86。
顺便提一下,在x86上得出错误结果是JIT编译器中的一个bug导致的。它生成了以下代码:
      double r = Math.Sqrt(v1 * v1);
00000006  fld         dword ptr ds:[009D1578h] 
0000000c  fsqrt            
0000000e  fstp        qword ptr [ebp-8] 

在发布模式下,代码优化器删除了fmul指令。毫无疑问,这是由于它看到double.MaxValue时触发的。这是一个错误,您可以在connect.microsoft.com上报告它。但我很确定他们不会修复它。


4

这篇文章与为什么在不同的计算机上进行浮点数计算会得到不同的结果?几乎一模一样。我的回答也适用于这个问题。简而言之,由于硬件细节的不同,不同的硬件允许给出更或更少准确的结果。

如何避免这种情况发生?既然问题出在芯片上,你有两个选择:(1)不要使用浮点数进行任何数学运算,而是使用整数进行所有数学运算。整数运算在芯片之间是100%一致的。或者(2)要求所有客户端使用与您开发相同的硬件。

请注意,如果您选择(2),则可能仍然会遇到问题。小细节,例如编译程序时是否启用了调试模式,可以改变浮点计算是否以额外的精度进行。这可能导致调试和发布版本之间的不一致结果,这也是意外和令人困惑的。如果您对一致性的要求比速度的要求更重要,则必须实现自己的浮点库,该库在整数中执行所有计算。


不幸的是,我们需要程序在64位Windows上以纯64位运行。这是一个工程应用程序,在整数中进行所有数学计算是不切实际的。 - david.healed

2

我在调试和发布模式下尝试了x86和x64:

x86 debug:   Double.MaxValue
x64 debug:   Infinity
x86 release: Infinity
x64 release: Infinity

看起来只有在调试模式下才会得到那个结果。

不确定为什么会有差异,但调试模式下x86代码如下:

            double r = Math.Sqrt(v1 * v1);
00025bda  fld         qword ptr [ebp-44h] 
00025bdd  fmul        st,st(0) 
00025bdf  fsqrt            
00025be1  fstp        qword ptr [ebp-5Ch] 
00025be4  fld         qword ptr [ebp-5Ch] 
00025be7  fstp        qword ptr [ebp-4Ch] 

在发布模式下,代码与此相同:

            double r = Math.Sqrt(v1 * v1);
00000027  fld         qword ptr [ebp-8] 
0000002a  fmul        st,st(0) 
0000002c  fsqrt            
0000002e  fstp        qword ptr [ebp-18h] 
00000031  fld         qword ptr [ebp-18h] 
00000034  fstp        qword ptr [ebp-10h]

你没有看到 JIT 优化的代码。在“工具+选项”中,选择“调试”,取消选中“抑制 JIT 优化”。你会发现 JIT 编译器不会生成 fmul 指令。这是一个 bug。 - Hans Passant

1
问题在于Math.Sqrt期望一个double类型的参数。v1 * v1不能被存储为double类型并且会导致溢出,从而导致未定义的行为

1
没有IEEE 754的未定义行为。溢出的“double”被定义为正无穷大,如何进行进一步操作的规则非常清晰。 - Joey

1

double.MaxValue * double.MaxValue 会导致溢出。

你应该避免计算溢出,而不是依赖于你所报告的32位行为(正如评论中所述,这似乎不太可能)。

[32位和64位版本是否具有相同的配置和设置?]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接