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

请考虑以下代码:

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

r = double.MaxValue在32位机器上r = 64位机器上的无穷大

我们在32位机器上开发,因此在客户通知之前不会发现问题。 为什么会发生这种不一致? 如何防止这种情况发生?

由于FPU的工作方式,x86指令集具有棘手的浮点一致性问题。 使用比存储在double中更高位的内部计算执行内部计算,从而将数字从FPU堆栈刷新到内存时导致截断。

在x64 JIT编译器中得到修复,它使用SSE指令,SSE寄存器的大小与double相同。

当您的计算测试浮点精度和范围的边界时,这将打字。 你永远不想接近需要超过15位有效数字,你永远不想接近10E308或10E-308。 你当然不想想象最大的可表示价值。 这绝不是一个真正的问题,代表物理量的数字不会接近。

利用这个机会找出你的计算有什么问题。 运行与客户正在使用的操作系统和硬件相同的操作系统和硬件非常重要,这样您就可以获得所需的机器。 仅在x86计算机上测试的运输代码未经过测试。

Q&D修复程序是Project + Properties,Compile选项卡,Platform Target = x86。


Fwiw,x86上的错误结果是由JIT编译器中的错误引起的。 它生成此代码:

  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上报告。 很确定他们不会修复它。

这是一个重复的

为什么这个浮点计算在不同的机器上给出不同的结果?

我对这个问题的回答也回答了这个问题。 简而言之:根据硬件的细节,允许不同的硬件提供或多或少的准确结果。

如何防止它发生? 由于问题在芯片上,你有两个选择。 (1)不要对浮点数进行任何数学运算。 用整数做全部数学运算。 整数数学在芯片与芯片之间100%一致。 或者(2)要求所有客户使用与您开发相同的硬件。

请注意,如果您选择(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] 

问题是Math.Sqrt期望一个double作为参数。 v1 * v1不能存储为double和overflow, 导致未定义的行为

double.MaxValue * double.MaxValue是一个溢出。

您应该避免计算溢出而不是依赖于您报告的32位行为(评论时看起来似乎是unlikey)。

[32位和64位是否构建相同的配置和设置?]