为啥 Math.Exp 在 32 位和 64 位之间给出不同的结果,相同的输入,相同的硬件

Posted

技术标签:

【中文标题】为啥 Math.Exp 在 32 位和 64 位之间给出不同的结果,相同的输入,相同的硬件【英文标题】:Why does Math.Exp give different results between 32-bit and 64-bit, with same input, same hardware为什么 Math.Exp 在 32 位和 64 位之间给出不同的结果,相同的输入,相同的硬件 【发布时间】:2011-04-30 10:52:52 【问题描述】:

我正在使用带有 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 选择特定于平台的高性能表示 浮点数,直到它们被放置在存储位置。例如,它可能能够离开 硬件寄存器中的浮点变量,提供比用户要求的精度更高的精度。在 分区 I 69 同时,CIL 生成器可以通过以下方式强制操作遵守特定语言的表示规则 使用转换指令。结束理由]

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

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

【问题讨论】:

+1 有趣。我在我的机器上看到完全相同的症状,并且在 x86/anycpu 之间切换会改变输出。 您的最后一段不正确。 32 位版本会更正确,因为它使用 80 位扩展精度 x87 FPU,而 64 位版本将使用更快、更一致的 SSE2。 Difference in floating point arithmetics between x86 and x64的可能重复 这么多重复:C# - Inconsistent math operation result on 32-bit and 64-bit、Why would the same code yield different numeric results on 32 vs 64-bit machines?、Floating point calculation change depending on the compiler、Why does this floating-point calculation give different results on different machines?、Floating point mismatch between compilers 【参考方案1】:

是的舍入错误,它实际上不是同一个硬件。 32 位版本的目标是一组不同的指令和寄存器大小。

【讨论】:

这很有趣——你是说有一组不同的 FPU 指令?诚然,我不知道 Math.Exp 是如何实现的,无论是一条 FPU 指令还是多条。而且我会认为两个平台的 FPU 寄存器是相同的,因为我使用的是“双”类型。 我不知道 .NET 实现或 x64 fpu 的细节,但我没想到它们是相同的。您还从 int 转换为 double 这会引入错误。 我将把它标记为答案,因为我认为它提供了最详细的信息。我在这个 URL 上找到了更多信息,它解释了 32 位 .NET 使用 80 位 FPU 寄存器,而 64 位 .NET 使用 128 位 SSE 寄存器:manicai.net/comp/debugging/fpudiff @Yoshi SSE2 寄存器是 SIMD 寄存器,即它们支持 128 位寄存器中的多个数据。这并不意味着它是一个 128 位的值。取而代之的是两个 64 位值(如果代码未矢量化,则上一个未使用),因此精度将低于 80 位 x87【参考方案2】:

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

【讨论】:

我(认为)我理解这一点,但是在相同硬件上对相同输入进行相同计算时发生的任何舍入错误至少应该是一致的,对吧?还是由于其他一些因素无法保证?

以上是关于为啥 Math.Exp 在 32 位和 64 位之间给出不同的结果,相同的输入,相同的硬件的主要内容,如果未能解决你的问题,请参考以下文章

为啥 32 位和 64 位 numpy/pandas 之间存在差异

为啥 .NET 不支持 32 位的 SSE(而 ryujit 64 位可以)而 Mono 支持 32 位和 64 位?

32位与64位之谈

qt5.2版本开发环境在win7(64位)上能否同时安装32位和64位两种版本。

服务器windows 2003 安装SQL 2000+SP4

32位和64位有啥区别 32位和64位区别都有哪些