C# 中的双倍比较精度损失,加减双精度时发生精度损失

Posted

技术标签:

【中文标题】C# 中的双倍比较精度损失,加减双精度时发生精度损失【英文标题】:Double comparison precision loss in C#, accuracy loss happening when adding subtracting doubles 【发布时间】:2016-12-19 12:07:22 【问题描述】:

刚开始学习 C#。我计划将它用于繁重的数学模拟,包括数值求解。问题是在添加和减去double 以及比较时会出现精度损失。代码及其返回的内容(以 cmets 为单位)如下:

namespace ex3

    class Program
    
        static void Main(string[] args)
        

            double x = 1e-20, foo = 4.0;

            Console.WriteLine((x + foo)); // prints 4
            Console.WriteLine((x - foo)); // prints -4
            Console.WriteLine((x + foo)==foo); // prints True BUT THIS IS FALSE!!!
        
    

不胜感激任何帮助和澄清!

让我困惑的是(x + foo)==foo 返回True

【问题讨论】:

请记住,不建议使用 == 运算符比较浮点数。请参阅this post 了解如何进行操作。 @Marcelo 谢谢!您能否发布答案中的链接(比较浮点数的功能)?我觉得它很有用。 我的评论里也有。 另外看看十进制类型 - 范围更小,但比双精度更好:msdn.microsoft.com/en-us/library/364x0z75.aspx 【参考方案1】:

查看 double 的 MSDN 参考:https://msdn.microsoft.com/en-AU/library/678hzkk9.aspx

它指出double 的精度为 15 到 16 位。

但就位数而言,1e-204.0 之间的差异是 20 位数。仅仅尝试在4.0 中添加或减去1e-20 就意味着1e-20 丢失了,因为它不能满足15 到16 位的精度。

所以,就double 而言,4.0 + 1e-20 == 4.04.0 - 1e-20 == 4.0

【讨论】:

那么为什么在 OP 的示例中 (x + foo) == foo 为假? @xr280xr - 不是。 OP 说// prints True BUT THIS IS FALSE!!! 意味着代码(x + foo) == foo 返回True,但在数学上它应该是False【参考方案2】:

对 Enigmativity 的回答的补充:

要使其工作,您需要更高的精度,并且 decimal 的精度为 28 到 29 位,基数为 10:

decimal x = 1e-20m, foo = 4.0m;
Console.WriteLine((x + foo)); // prints 4.00000000000000000001
Console.WriteLine((x - foo)); // prints -3.99999999999999999999 
Console.WriteLine((x + foo) == foo); // prints false.

但请注意,小数确实具有较大的精度,但具有较低的范围。查看更多关于十进制的信息here

【讨论】:

【参考方案3】:

您正在寻找的可能是十进制结构 (https://msdn.microsoft.com/en-us/library/system.decimal.aspx)。 Doubles 不能以您正在寻找的精度正确表示那种值 (C# double to decimal precision loss)。相反,请尝试使用 Decimal 类,如下所示:

decimal x = 1e-20M, foo = 4.0M;

        Console.WriteLine(Decimal.Add(x, foo)); //prints 4,0000000000000000001
        Console.WriteLine(Decimal.Add(x, -foo)); //prints -3,9999999999999999999
        Console.WriteLine(Decimal.Add(x, foo) == foo); // prints false

【讨论】:

【参考方案4】:

这不是 C# 的问题,而是您的计算机的问题。它并不是很复杂或难以理解,但它是一个很长的阅读。如果您想深入了解计算机的工作原理,您应该阅读this article。

一个优秀的 TLDR 网站 imho 是比上述文章更好的主题介绍是这个:

http://floating-point-gui.de/


我将为您提供一个非常简短的说明,说明发生了什么,但您当然应该至少阅读该网站,以避免将来出现麻烦,因为您的应用领域需要深入了解这些知识。

会发生以下情况:您有 1e-20,它比 1.11e-16 小。另一个数字被称为计算机上的双精度机器 epsilon(很可能)。如果您将等于或大于 1 的数字添加到小于机器 epsilon 的值,它将被四舍五入,返回大数。这是由于 IEEE 754 表示。这意味着在加法发生后,结果是“正确的”(就好像你有无限精度),结果以有限/有限精度的格式存储,将 4.00....001 舍入到 4,因为取整误差小于1.11e-16,可以接受。

【讨论】:

感谢您的详尽解释以及您的宝贵时间。希望可以接受 2 个答案。 这与计算机本身没有任何关系。 100% 与 C#(和 .NET)以及框架设计者选择的浮点数表示有关。 另外,您不应在外部链接中提供答案。您应该将内容放在答案中,并且仅提供支持您所写内容的链接。 每台具有 64 位 fp 算法的计算机的行为都是相同的。这不是 C#,而是 IEEE 754 的工作方式。如果他们以不同的方式实现双精度,那么它们实际上就不会是每种语言都称为双精度浮点数 我确实回答了这个问题,只是提供了进一步的阅读,因为它对于一个 QA 网站来说太长了,而且是必不可少的 imo。

以上是关于C# 中的双倍比较精度损失,加减双精度时发生精度损失的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 到 SQL Server 的精度损失

这种精度损失发生在哪里以及如何防止它?

数值溢出与精度损失

double发生精度丢失的解决办法

js加减乘除精度

犰狳:矩阵乘法精度损失