.net 4.5.2 中对 Math.Exp 或双重实现的更改

Posted

技术标签:

【中文标题】.net 4.5.2 中对 Math.Exp 或双重实现的更改【英文标题】:Changes to Math.Exp or double implementation in .net 4.5.2 【发布时间】:2015-10-15 16:09:02 【问题描述】:

如果我运行语句

Math.Exp(113.62826122038274).ToString("R")

在安装了 .net 4.5.1 的机器上,然后我得到答案

2.2290860617259248E+49

但是,如果我在安装了 .net framework 4.5.2 的机器上运行相同的命令,那么我会得到答案

2.2290860617259246E+49

(即最后一位数字发生变化)

我意识到这在纯数字方面是微不足道的,但有谁知道 .net 4.5.2 中所做的任何更改可以解释这种更改?

(我不喜欢另一种结果,我只是想了解它为什么会改变)

如果我输出

The input in roundtrip format
The input converted to a long via BitConverter.DoubleToInt64Bits
Math.Exp in roundtrip format
Math.Exp converted to a long via BitConverter.DoubleToInt64Bits

然后在 4.5.1 我得到

113.62826122038274
4637696294982039780
2.2290860617259248E+49
5345351685623826106

在 4.5.2 上我得到:

113.62826122038274
4637696294982039780
2.2290860617259246E+49
5345351685623826105

所以对于完全相同的输入,我得到不同的输出(从位可以看出,因此不涉及往返格式)

更多细节:

使用VS2015编译一次

我运行二进制文件的两台机器都是 64 位的

一个安装了 .net 4.5.1,另一个安装了 4.5.2

只是为了清楚起见:字符串转换无关紧要......无论是否涉及字符串转换,我都会得到结果的变化。我提到这纯粹是为了展示这种变化。

【问题讨论】:

是否也安装了net46? @AndreasMüller - 不,只有 4.5.2,尽管 4.6 与 4.5.2 的答案相同 - 更改似乎是由 4.5.2 而不是 4.6 引入的 @WaiHaLee 查看更新后的问题 浮点运算由 CPU 执行,而不是框架。 CPU有区别吗?差异也可能是由一个使用 FPU 的框架和另一个使用 SSE 的框架引起的。 4.5.2 没有做出这样的改变,但 4.6 确实引入了 SIMD 操作。 4.6 是 4.5 的二进制替换,所以 也许 这就是造成差异的原因 在我的机器上,4.5.2 我得到了你的第一个结果,最后一个数字以 8 结尾,而不是 6,2.2290860617259248E+49 【参考方案1】:

叹息,浮点数学的奥秘一直困扰着程序员。它与框架版本没有任何关系。相关设置是 Project > Properties > Build 选项卡。

平台目标 = x86:2.2290860617259248E+49 平台目标 = AnyCPU 或 x64:2.2290860617259246E+49

如果您在 32 位操作系统上运行该程序,那么您总是会得到第一个结果。请注意,往返格式被过度指定,它包含的数字比双精度数可以存储的多。是 15。算上它们,你得到 16。这确保了双精度的 二进制 表示,1 和 0 是相同的。两个值之间的差异是尾数中的最低有效位。

LSB 不一样的原因是x86 jitter 阻碍了为the FPU 生成代码。它具有非常不受欢迎的特性,即使用 more 位的精度而不是 double 可以存储的。 80位而不是64位。理论上可以产生更准确的计算结果。它确实如此,但rarely in a reproducible way。代码的微小变化可能会导致计算结果发生很大变化。仅仅运行带有调试器的代码就可以改变结果,因为这会禁用优化器。

英特尔用 SSE2 指令集修复了这个错误,完全取代了 FPU 的浮点数学指令。它不使用额外的精度,双精度总是有 64 位。由于计算结果现在不再依赖于中间存储这一非常理想的特性,它现在更加一致。但不太准确。

x86 jitter 使用 FPU 指令是历史性的意外。 2002 年发布时,周围没有足够的处理器支持 SSE2。该事故无法再修复,因为它改变了程序的可观察行为。 x64 抖动不是问题,64 位处理器保证也支持 SSE2。

32 位进程使用使用 FPU 代码的 exp() 函数。 64 位进程使用使用 SSE 代码的 exp() 函数。结果可能相差一个 LSB。但仍精确到 15 位有效数字,即 2.229086061725925E+49。使用 double,您可以从数学中获得所有期望。

【讨论】:

我不相信这可以解释“Math.Exp 在安装 .NET 4.6.1 并应用 KB3098785 后返回不同的结果”connect.microsoft.com/VisualStudio/feedback/details/2486915/… 我告诉过你该怎么做。自己尝试一下,更改平台目标并观察 x86 生成 0.71612515940795685 和 x64 生成 0.71612515940795696。与框架版本无关,与 FPU 与 SSE2 无关。 在我的情况下,任何 CPU 都给出了 2.2290860617259248E+49,因为设置了 Prefer 32bit。但总体而言,您的回答也是如此。【参考方案2】:

.NET 使用 CRT 中的数学库函数来进行这些计算。 .NET 使用的 CRT 经常随每个版本更新,因此您可以预期 .NET 版本之间的结果会发生变化,但是,它们将始终在承诺的 +/1ulp 范围内。

【讨论】:

@flq - C 运行时。它的编译器附带的 Microsoft 的 C 标准库。这个 SO 帖子有很多很好的信息:***.com/questions/2766233/…【参考方案3】:

我遇到了同样的问题,但是我只是在安装 .Net 4.6 后才得到它。

.Net 4.6安装升级c:\windows\system32\msvcr120_clr0400.dll和c:\windows\syswow64\msvc120_clr0400.dll。

在安装之前,这些 DLL 具有文件属性->详细信息:“文件版本 12.0.51689.34249”和“产品名称 Microsoft Visual Studio 12 CTP”

安装 .net 4.6 后,这些文件具有“文件版本 12.0.52512.0”和“产品名称 Microsoft Visual Studio 2013”​​

我调整了我的测试以包含您的示例,并且通过在此 DLL 的版本之间翻转,我看到了与您相同的前后数字。

(在更新这些 DLL 之前,我们的测试套件在 .net 版本 4、4.5、4.5.1 或 4.5.2 上运行时没有显示任何更改的结果。)

【讨论】:

【参考方案4】:

为了展示针对不同 .NET 版本的项目如何影响从字符串的双重转换,我构建了 4 个针对不同版本的项目,这些项目都在具有 .NET 4.6 的同一开发机器上运行。

这是代码

double foo = Convert.ToDouble("33.94140881672595");

这是输出

33.941408816725954 (.NET 4)

33.941408816725946 (.NET 4.5)

33.941408816725946 (.NET 4.5.2)

33.941408816725946 (.NET 4.6)

所以,.NET 4 之后转换方法肯定发生了变化

【讨论】:

以上是关于.net 4.5.2 中对 Math.Exp 或双重实现的更改的主要内容,如果未能解决你的问题,请参考以下文章

math.exp(2) 和 math.e**2 之间的区别 [重复]

Java 中 Math.exp 函数的局限性

如何在 TensorFlow 中对数据集进行切片?

java中API怎么调用啊?

math对象和date对象

从 .NET 4.5 MVC 4 升级到 .NET 4.5.2 MVC 5.2