【问题标题】:ToString has a different behavior between .NET 462 and .NET Core 3.1 [duplicate]ToString 在 .NET 462 和 .NET Core 3.1 之间具有不同的行为 [重复]
【发布时间】:2020-11-18 17:23:05
【问题描述】:

我正在将一个应用程序从 .NET Framework 4.6.2 迁移到 .NET Core 3.1,但我的单元测试在我没想到的地方失败了。

经过一些计算,我最终得到了一个 16 位的double。根据调试器,无论我运行.NET462 还是.NETCore31 代码,我都得到了完全相同的值。当我“序列化”这个值时,就会出现差异。在.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)

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

【问题讨论】:

  • .NET Core 大大改进了浮点数。查看接受的答案here,因为我认为它会回答您的问题
  • 我不确定链接的答案是否回答了这个问题——这个问题的位数在较新版本的 .NET Core 上递减
  • @canton7 但您提供的“答案”链接到同一篇文章并引用了文章的相同部分......
  • @AlexeiLevenkov 我确实使用了相同的来源,但该答案集中在 .NET Core 3.0 之前的版本上,生成无法往返的字符串。这个问题不是这样,ToString("R") 的结果可以在 .NET Core 3.0 之前很好地进行往返。虽然其他答案确实引用了“ToString("R") 现在将返回最短的可往返字符串”,但它没有解释这如何适用于 OP 的问题。通过显示"4.058478924107704" 被解析回4.0584789241077042,我尝试进一步扩展“最短的可往返字符串”
  • 自 2019 年 2 月以来,CoreCLR 发生了重大变化。它曾经在 CLR 中使用 C++ 实现,使用 15 或 17 位有效数字。现在用 C# 编写,通过 Grisu 算法产生最短的可能。

标签: c# .net .net-core


【解决方案1】:

在 .NET Core 3.0 中对浮点进行了许多更改,Tanner lists in this article

我认为与我们有关的是:

ToString()ToString("G")ToString("R") 现在将返回最短的可往返字符串。这可以确保用户最终得到默认情况下可以使用的东西。有问题的一个例子是Math.PI.ToString(),其中先前返回的字符串(对于ToString()ToString("G"))是3.14159265358979;相反,它应该返回3.1415926535897931。前一个结果在解析时返回一个值,该值在内部偏离Math.PI 的实际值 7 ULP(最后一个单位)。这意味着用户很容易陷入在需要序列化/反序列化浮点值时意外丢失某些精度的情况。

因此,您的 4.0584789241077042 值现在作为可以往返的最短值进行往返。换句话说,即使结果字符串缺少最后一个小数位 ("4.058478924107704"),将其解析回双精度仍然会得到 4.0584789241077042,因为与 4.058478924107704 最接近的值可以由IEEE 双精度为 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 2017-09-09
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多