【问题标题】:Why would a variable of type double have an unexpected result?为什么 double 类型的变量会产生意想不到的结果?
【发布时间】:2011-07-06 07:27:01
【问题描述】:

我的完整性检查失败了,因为双变量不包含预期的结果,这真的很奇怪。

double a = 1117.54 + 8561.64 + 13197.37;
double b = 22876.55;
Console.WriteLine("{0} == {1}: {2}", a, b, a == b);

给我们这个输出:

22876.55 == 22876.55: False

进一步检查表明,变量 a 实际上包含值 22876.550000000003。

这在 vb.net 中也可以重现。 我神智正常吗?怎么回事?

【问题讨论】:

  • 浮点类型不能总是表示精确十进制值。根据您的观点,它是“已知错误”或“设计使然”。尝试改用decimal 类型。
  • 你很清醒,但你的期望不合适。阅读csharpindepth.com/Articles/General/FloatingPoint.aspx
  • @Cody Okay decimal 可能是一种解决方法。但是,在所有其他已编写的应用程序的所有其他代码中,我如何才能减轻任何其他可能的此类不一致?
  • Why don't operations on double-precision values give expected results? 的可能重复项以及许多其他问题!
  • 我怀疑相关问题现在都出现了,因为添加了标签 dan04:“floating-accuracy”。这是一个众所周知的现象,如果你不知道的话,它肯定是无数(嘿!)错误的来源。

标签: .net variables floating-point floating-accuracy


【解决方案1】:

浮点类型并不总是能够准确地表示其精确的十进制值。根据您的观点,它是“已知错误”或“设计使然”。无论哪种方式,它都是浮点类型的内部表示的结果,也是常见的错误来源。

这个问题也几乎是不可避免的,因为没有编写一个复杂的计算机代数系统来表示符号值,而不是数字类型。打开 Windows 计算器,确定 4 的平方根,然后从该值中减去 2。您会得到一些非常接近 0 的无意义浮点数,但不是完全 0。平方根计算的结果没有存储为完全 2,所以当你从中减去 2 时,你会得到一个“意外”的结果。出乎意料,也就是说,除非您知道有关以 2 为底的算术的肮脏小秘密。

如果您很好奇,您可以去几个地方找到更多关于为什么会这样的信息。 Jon Skeet 写了an article 解释.NET Framework 上下文中的二进制浮点运算。如果您有时间,还应该仔细阅读命名恰当的出版物What Every Computer Scientist Should Know About Floating-Point Arithmetic

但最重要的是,您不应该期望能够将浮点运算的结果与浮点文字进行比较。在这种特定情况下,您可以尝试改用 decimal 类型。这并不是一个真正的“解决方案”(请参阅​​其他答案,那些可怕的数学概念,如 epsilon),但结果通常更可预测,因为 decimal 类型更擅长准确表示以 10 为底的数字(例如在货币和财务计算)。

【讨论】:

    【解决方案2】:

    这是浮点舍入,这是设计使然 - 除了一组非常罕见的特殊情况外,您永远不应期望浮点变量与任何其他浮点变量完全相等。

    另见this question

    【讨论】:

    • 我看不出这是(故意的)设计,真是太疯狂了。
    • @Wesley 这是“有意的”设计,因为不可能用有限的位数来表示可能无限长的十进制值。
    • 强调doubles 以 2 为底存储,其中 0.1 十进制为 0.0 0011 0011 0011 0011... 重复。
    • 谢谢@Sharptooth,从今天开始我一定会更加关注花车:)
    【解决方案3】:

    正如尖牙所说,浮点变量是如何保存在内存中的。你可以阅读更多here

    还可以检查两个浮点数是否相等,您可以使用以下内容:

    double a = 1117.54 + 8561.64 + 13197.37;
    double b = 22876.55;
    Console.WriteLine("{0} == {1}: {2}", a, b, fabs(a-b) < 1e-9);
    

    它通过这些数字有多少不同来检查它的作用。如果它们的差异仅在点后的第 9 位之后,您可以假定它们是相等的。 要获得更高的精度,只需使用较低的 epsilon(两个数字之间的最大差值,以使它们被视为相等)。

    【讨论】:

      【解决方案4】:

      你很清醒。您只是在处理无法以任意数量的二进制数字完美存储的数字。您会在任何语言中看到这一点 - 舍入“错误”是浮点格式固有的,因此也存在于硬件中。

      如果您真的需要完美的比较,请尝试使用小数技巧:选择您将要处理的最小分数,并以此来表达所有内容。如果你愿意的话,你自己的个人普朗克常数。例如,您的示例代码将变为:

      int a = 111754 + 856164 + 1319737;
      int b = 2287655;
      //Convert back to decimal format for human consumption:
      Console.WriteLine("{0} == {1}: {2}", ((double)a)/100, ((double)b)/100, a == b);
      

      希望这会有所帮助!

      【讨论】:

      • @Wesley 欢迎您!它基于数据库如何存储这样的数字(通常用于货币,舍入错误会很不幸)。如果你使用这种方法,请记住,小数点后的每个精度数字都会带走小数点前的一个数字 - 如果你的数字非常大而且非常精确,你可能需要使用比 int 更大的东西来安全地安装它们。干杯!
      • 有趣的是,它是用于一个会计系统,过去使用这种编号系统,但我使用的 SDK 更改为使用双精度数,没有警告。不用说,这造成了很多麻烦。作为主要玩家也令人震惊! :p
      【解决方案5】:

      浮点存储数字的近似值(浮点数的精度约为小数点后 6-7 位)。

      因此,当您使用 fp 数字进行计算时,您通常会在每个数字中出现微小的表示错误,这些错误会被带到计算中。诸如乘法之类的操作会放大这些错误。如果您不小心,错误可能会变得很严重。

      最常见的问题是使用 == 来确定两个值是否精确相等,因为 2.999999 和 3.00000000 非常接近,但并不相等。由于 fp 表示的错误,最终得到接近但不相等的数字是很常见的,正如您所发现的那样。

      因此,与其说“我的数字是否完全等于 3.0”,我们必须说“我的数字是否足够接近 3.0,我对此感到满意?”。我们通过使用容差值进行测试来做到这一点,例如:“我的值是否大于 2.999 且小于 3.001”。写出数字时,您可以使用“{0:0.000}”之类的格式字符串对其进行舍入并删除它显示的微小错误。

      因此您可以通过以下方式实现您想要的(精确到小数点后 3 位):

      Console.WriteLine("{0:0.000} == {1:0.000}: {2}", a, b, Math.Abs(a - b) < 0.0001);
      

      【讨论】:

      • 实际上是 7-8 个十进制数字或更精确的 24 个二进制数字,相当于 0-9999999 范围内和 10000000-16277215 范围内的所有整数。后一个范围是“-8”数字的来源。数字的符号不影响精度。
      【解决方案6】:

      使用 Decimal 数据类型而不是 Double 数据类型。数字实数字面量要当作十进制,使用后缀 m 或 M。没有后缀 m,数字被当作双精度并产生编译错误。

      decimal a = 1117.54M + 8561.64M + 13197.37M;
      decimal b = 22876.55M;
      Console.WriteLine("{0} == {1}: {2}", a, b, a == b);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-17
        • 1970-01-01
        • 2020-04-19
        相关资源
        最近更新 更多