请考虑以下代码:
double v1 = double.MaxValue;
double r = Math.Sqrt(v1 * v1);
r = 32位机器上的double.MaxValue r = 64位机器上的无穷大
我们在32位机器上开发,因此在客户通知之前不会发现问题。为什么会发生这种不一致?如何防止这种情况发生?
答案 0 :(得分:21)
由于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 :(得分:3)
这是
的近似副本Why does this floating-point calculation give different results on different machines?
我对这个问题的回答也回答了这个问题。简而言之:根据硬件的细节,允许不同的硬件提供或多或少的准确结果。
如何防止它发生?由于问题在芯片上,你有两个选择。 (1)不要对浮点数进行任何数学运算。用整数做全部数学运算。整数数学在芯片与芯片之间100%一致。或者(2)要求所有客户使用与您开发相同的硬件。
请注意,如果您选择(2),那么您可能仍会遇到问题;程序是否编译为调试或零售的小细节可以改变浮点计算是否以额外的精度完成。这可能会导致调试和零售版本之间出现不一致的结果,这也是意外和令人困惑的。如果您对一致性的要求比您对速度的要求更重要,那么您将必须实现自己的浮点库,以整数进行所有计算。
答案 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]
答案 3 :(得分:1)
问题是Math.Sqrt期望双参数。 v1 * v1
不能存储为double并溢出导致未定义的行为。
答案 4 :(得分:1)
double.MaxValue * double.MaxValue
是一个溢出。
你应该避免计算溢出而不是依赖于你报告的32位行为(评论时看起来似乎是unlikey)。
[32位和64位构建相同的配置和设置吗?]