C# ToString在.NET462和.NETCore3.1之间有不同的行为

C# ToString在.NET462和.NETCore3.1之间有不同的行为,c#,.net,.net-core,C#,.net,.net Core,我正在将一个应用程序从.netframework4.6.2迁移到.netcore3.1,而我的单元测试在我没有预料到的地方失败了 经过一些计算,我得到了一个16位的双精度。根据调试器,无论是运行.NET462还是.NETCore31代码,我都得到了完全相同的值。当我“序列化”此值时,会出现差异。在.NETCore31版本中,最后一位数字丢失: 以下是一个例子: .NET .NET核心 这实际上不是一个问题,我的计算不需要这样的精度,但是有人知道为什么我会得到两个不同的结果吗?在.NET Core

我正在将一个应用程序从
.netframework4.6.2
迁移到
.netcore3.1
,而我的单元测试在我没有预料到的地方失败了

经过一些计算,我得到了一个16位的
双精度
。根据调试器,无论是运行
.NET462
还是
.NETCore31
代码,我都得到了完全相同的值。当我“序列化”此值时,会出现差异。在
.NETCore31
版本中,最后一位数字丢失:

以下是一个例子:

.NET

.NET核心


这实际上不是一个问题,我的计算不需要这样的精度,但是有人知道为什么我会得到两个不同的结果吗?

在.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”
),将其解析回double仍然会得到
4.0584789241077042
,这是因为IEEE double可以表示的最接近
4.058478924107704
的值是
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

NET内核大大改进了浮点运算。请查看已接受的答案,因为我认为它将回答您的问题。我不确定链接的答案是否回答了此问题。在较新版本的.NET上,此问题的位数正在减少Core@canton7而你却提供了“答案”链接到同一篇文章并引用了文章的相同部分…@AlexeiLevenkov我确实使用了相同的来源,但答案集中在.NET Core 3.0之前的版本上,这些版本生成的字符串不能往返。这个问题不是这样的,在.NETCore3.0之前,
ToString(“R”)
的结果可以很好地进行往返。虽然另一个答案引用了“ToString(“R”)现在将返回最短的roundtrippable字符串”,但它没有解释这如何适用于OP的问题。我试图对“最短的roundtrippable字符串”进行更多的扩展,展示了自2019年2月以来,CORECRL中的
“4.058478924107704”
被解析回
4.0584789241077042
的巨大变化。它使用C++实现,在CLR内部使用15或17个有效数字。现在用C#编写,通过Grisu算法生成尽可能短的值。
4.0584789241077042.ToString("R", CultureInfo.InvariantCulture);
// "4.058478924107704" (the last digit is gone)
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