为什么在相同的硬件和输入下,Math.Exp在32位和64位之间会产生不同的结果?

10
我正在使用带有PlatformTarget x64和x86的.NET 2.0。 我将相同的输入数字提供给Math.Exp,并且在两个平台上返回不同的结果。
MSDN说您不能依赖于文字/解析的Double来表示平台之间相同的数字,但我认为我下面使用的Int64BitsToDouble避免了这个问题,并保证了在两个平台上将相同的输入提供给Math.Exp。
我的问题是为什么结果不同? 我本来以为:
  • 输入以相同的方式存储(双精度/ 64位精度)
  • 无论处理器的位数如何,FPU都会执行相同的计算
  • 输出以相同的方式存储
我知道通常情况下不应该在第15/17位之后比较浮点数,但我对这里的不一致感到困惑,因为看起来是在相同的硬件上执行相同的操作。
有人知道内部发生了什么吗?
double d = BitConverter.Int64BitsToDouble(-4648784593573222648L); // same as Double.Parse("-0.0068846153846153849") but with no concern about losing digits in conversion
Debug.Assert(d.ToString("G17") == "-0.0068846153846153849"
    && BitConverter.DoubleToInt64Bits(d) == -4648784593573222648L); // true on both 32 & 64 bit

double exp = Math.Exp(d);

Console.WriteLine("{0:G17} = {1}", exp, BitConverter.DoubleToInt64Bits(exp));
// 64-bit: 0.99313902928727449 = 4607120620669726947
// 32-bit: 0.9931390292872746  = 4607120620669726948

无论 JIT 开启或关闭,两个平台的结果都是一致的。

[编辑]

我对下面的答案不完全满意,所以在这里提供一些我搜索到的更多细节。

http://www.manicai.net/comp/debugging/fpudiff/ 说:

因此,32 位使用 80 位 FPU 寄存器,64 位使用 128 位 SSE 寄存器。

CLI 标准表示,如果硬件支持,则可以使用更高的精度来表示双精度数:

[基本原理:该设计允许 CLI 在将浮点数放置在存储位置之前选择特定于平台的高性能表示。例如,它可能能够将浮点变量保留在提供比用户请求的更高精度的硬件寄存器中。同时,CIL 生成器可以通过使用转换指令强制操作遵守语言特定的表示规则。结束基本原理]

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf(12.1.3 处理浮点数据类型)

我认为这就是发生的事情,因为在 Double 标准的 15 位精度后,结果不同了。64 位的 Math.Exp 结果更精确(它多了一位数字),因为在内部,64 位的 .NET 使用一个比 32 位的 .NET 更精确的 FPU 寄存器。


有趣。我在我的机器上看到完全相同的症状,而在x86 / anycpu之间切换会改变输出。 - sisve
1
你的最后一段是错误的。32位版本会更准确,因为它使用80位扩展精度x87 FPU,而64位版本将使用更快和更一致的SSE2。 - phuclv
2个回答

4

是的,会有舍入误差,并且实际上它们不是相同的硬件。32位版本针对不同的指令集和寄存器大小。


1
很有趣 - 你是在说有一组不同的FPU指令吗?诚然,我不知道Math.Exp是如何实现的,它是一个FPU指令还是多个指令。而且我认为FPU寄存器在两个平台上都是相同的,因为我正在使用“double”类型。 - Yoshi
我不了解.NET实现或x64 fpu的细节,但我不认为它们应该是相同的。您还将int转换为double,这会引入错误。 - winwaed
1
我将把这个标记为答案,因为我认为它提供了最详细的信息。我在这个URL找到了更多的信息,它解释说32位的.NET使用80位FPU寄存器,而64位的.NET使用128位SSE寄存器:http://www.manicai.net/comp/debugging/fpudiff/ - Yoshi
@Yoshi SSE2寄存器是SIMD寄存器,即它们支持在128位寄存器中处理多个数据。这并不意味着它是一个单一的128位值。相反,它是两个64位值(如果代码未向量化,则上部分未使用),因此精度比80位x87慢。 - phuclv

2

使用Double类型会出现舍入误差,因为二进制中的分数很快变得非常大。如果使用Decimal类型可能会有所帮助。


我认为我理解了,但是在相同的硬件上对相同输入进行相同计算时发生的任何舍入误差至少应该是一致的,对吗?或者由于其他因素没有保证这一点吗? - Yoshi

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