我正在将一个应用程序从.NET Framework 4.6.2
迁移到.NET Core 3.1
,我的单元测试在一个我意想不到的地方失败了。
经过一些计算,我得到了一个16位的double
。根据调试器,无论是运行.NET462
还是.NETCore31
代码,我都得到了完全相同的值。当I";串行化";此值。在.NETCore31
版本中,最后一位数字丢失:
这里有一个例子:
.NET
4.0584789241077042.ToString("R", CultureInfo.InvariantCulture)
// "4.0584789241077042" (the exact same number)
.NET核心
4.0584789241077042.ToString("R", CultureInfo.InvariantCulture);
// "4.058478924107704" (the last digit is gone)
这实际上不是一个问题,我的计算不需要这样的精度,但有人知道为什么我会得到两个不同的结果吗
Tanner在本文中列出的.NET Core 3.0中对浮点进行了许多更改。
我认为我们关心的是:
ToString()
、ToString("G")
和ToString("R")
现在将返回最短的可往返字符串。这确保了用户最终得到的是默认情况下可以工作的东西。有问题的一个例子是Math.PI.ToString()
,其中先前返回的字符串(对于ToString()
和ToString("G")
(是3.14159265358979
;相反,它应该返回3.1415926535897931
。解析之前的结果时,返回的值与Math.PI
的实际值内部相差7 ULP(最后一位为单位(。这意味着用户很容易陷入这样一种情况:当需要序列化/反序列化浮点值时,他们会意外地丢失浮点值的一些精度。
所以4.0584789241077042
的值现在是可以往返的最短值。换言之,即使生成的字符串缺少最后一个小数点("4.058478924107704"
(,由于与4.058478924107704
最接近的值(可以由IEEE双精度表示(是4.0584789241077042
,因此将其解析回双精度仍然会得到4.0584789241077042
。
double original = 4.0584789241077042;
Console.WriteLine("Original: {0:G17}", original);
// Original: 4.0584789241077042
string s = original.ToString("R", CultureInfo.InvariantCulture);
Console.WriteLine("Rouble-trippable: {0}", s);
// Rouble-trippable: 4.058478924107704
double parsed = double.Parse(s, CultureInfo.InvariantCulture);
Console.WriteLine("Parsed: {0:G17}", parsed);
// Parsed: 4.0584789241077042
Console.WriteLine("Original == Parsed: {0}", original == parsed);
// Original == Parsed: True