【问题标题】:C# - Inconsistent math operation result on 32-bit and 64-bitC# - 32 位和 64 位的数学运算结果不一致
【发布时间】:2011-01-28 12:18:55
【问题描述】:

考虑以下代码:

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

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

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

【问题讨论】:

  • 在此处的 32 位和 64 位计算机上输出 +inf

标签: c#


【解决方案1】:

由于 FPU 的工作方式,x86 指令集存在棘手的浮点一致性问题。内部计算使用的有效位比双精度中存储的多,当数字从 FPU 堆栈刷新到内存时会导致截断。

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

当您的计算测试浮点精度和范围的边界时,这会让您感到厌烦。您永远不想接近需要超过 15 个有效数字,您永远不想接近 10E308 或 10E-308。您当然永远不想平方最大的可表示值。这从来都不是真正的问题,代表物理量的数字并不接近。

利用这个机会找出您的计算出了什么问题。运行与客户使用的操作系统和硬件相同的操作系统和硬件是非常重要的,这是您获得所需机器的重要时间。仅在 x86 机器上测试的发布代码未经过测试。

Q&D 修复是项目 + 属性,编译选项卡,平台目标 = 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 上报告它。很确定他们不会修复它。

【讨论】:

    【解决方案2】:

    这几乎是

    的复制品

    Why does this floating-point calculation give different results on different machines?

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

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

    请注意,如果您选择(2),那么您可能仍然会遇到问题;诸如程序是编译调试还是零售的小细节可以改变浮点计算是否以额外的精度完成。这可能会导致调试和零售版本之间的结果不一致,这也是意料之外和令人困惑的。如果您对一致性的要求比对速度的要求更重要,那么您将必须实现自己的浮点库,该库以整数进行所有计算。

    【讨论】:

    • 不幸的是,我们需要程序在 64 位 Windows 上作为纯 64 位运行。这是一个工程应用程序,其中以整数进行所有数学运算是不切实际的。
    【解决方案3】:

    我在 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] 
    

    和release模式下的代码一样:

                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 优化”。您会看到 JITter 不会费心生成 fmul 指令。这是一个错误。
    【解决方案4】:

    问题是 Math.Sqrt 需要一个 double 作为参数。 v1 * v1 不能存储为双精度并溢出导致未定义的行为

    【讨论】:

    • IEEE 754 没有未定义的行为。溢出的double 被定义为正无穷大,如何进行进一步操作的规则非常明确。
    【解决方案5】:

    double.MaxValue * double.MaxValue 是溢出。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多