【问题标题】:Why do I see a double variable initialized to some value like 21.4 as 21.399999618530273?为什么我看到一个 double 变量初始化为 21.4 之类的某个值作为 21.399999618530273?
【发布时间】:2010-09-15 16:54:48
【问题描述】:
double r = 11.631;
double theta = 21.4;

在调试器中,这些显示为11.63100000000000021.399999618530273

我怎样才能避免这种情况?

【问题讨论】:

    标签: floating-point precision


    【解决方案1】:

    这些accuracy problems 是由浮点数的internal representation 引起的,您无能为力。

    顺便说一句,在运行时打印这些值通常仍然会产生正确的结果,至少使用现代 C++ 编译器是这样。对于大多数操作来说,这不是什么大问题。

    【讨论】:

    • 程序员应该注意这一点,尤其是当他们使用非常大或非常小的数字时,准确性可能很重要。
    • 不一定非常大或非常小——无论整体数字大小如何,浮点精度都是相同的。问题是当您混合非常大和非常小的值时,例如将它们相加。
    • 黑暗——这不是真的。可表示值的空间在 0 附近更加密集,而在无穷大时更加稀疏(例如,2^24+1 不能使用 IEEE 浮点标准精确表示 32 位双精度数)跨度>
    • 指数稀疏,事实上,因为你正在应用一个指数。
    【解决方案2】:

    我喜欢Joel's explanation,它处理 Excel 2007 中类似的二进制浮点精度问题:

    看到最后有很多 0110 0110 0110 吗?那是因为 0.1 没有精确的二进制表示...它是一个重复的二进制数。这有点像 1/3 没有十进制表示。 1/3 是 0.33333333,你必须永远写 3。如果你失去耐心,你会得到一些不准确的东西。

    所以你可以想象一下,如果你尝试做 3*1/3,而你没有时间永远写 3,那么你可以想象得到的结果是 0.99999999,而不是 1,而人们会对你的错误感到生气。

    【讨论】:

    • 如果您尝试做 3*1/3,您将三乘以一并得到三。然后你将三除以三,没有人应该生气。我假设 Joel 的意思是 3*(1/3)。
    • @Nosredna 这取决于您使用的语言是否对*/ 具有更高的运算符优先级。
    【解决方案3】:

    如果你有类似的值:

    double theta = 21.4;
    

    而你想做的事:

    if (theta == 21.4)
    {
    }
    

    你必须有点聪明,你需要检查 theta 的值是否真的接近 21.4,但不一定是那个值。

    if (fabs(theta - 21.4) <= 1e-6)
    {
    }
    

    【讨论】:

    • 双 theta = 21.4; bool b = theta == 21.4;// 这里 b 始终为真
    【解决方案4】:

    这部分是特定于平台的 - 我们不知道您使用的是什么平台。

    这也是了解您真正想要看到什么的情况。调试器正在向您展示 - 在某种程度上,无论如何 - 存储在变量中的精确值。在我的article on binary floating point numbers in .NET 中,有一个C# class 可以让您看到绝对准确 存储在双精度数中的数字。在线版本目前无法使用 - 我会尝试在其他网站上发布。

    鉴于调试器看到的是“实际”值,它必须对要显示的内容做出判断 - 它可以向您显示四舍五入到小数点后几位的值,或更精确的值。一些调试器在阅读开发人员的想法方面比其他调试器做得更好,但这是二进制浮点数的一个根本问题。

    【讨论】:

    • Jon,这个问题最初被标记为 C++/VC6,所以在有人认为此信息不重要并编辑了标签之前,我们实际上知道平台。
    【解决方案5】:

    如果您希望在精度范围内保持稳定性,请使用定点 decimal 类型。有开销,如果您希望转换为浮点数,则必须显式转换。如果您确实转换为浮点数,您将重新引入似乎困扰您的不稳定性。

    或者,您可以克服它并学习使用浮点运算的有限精度。例如,您可以使用舍入来使值收敛,或者您可以使用 epsilon 比较来描述容差。 “Epsilon”是您设置的定义容差的常数。例如,如果两个值相差在 0.0001 以内,您可以选择将它们视为相等。

    在我看来,您可以使用运算符重载来使 epsilon 比较透明。那会很酷。


    对于尾数指数表示,必须计算 EPSILON 以保持在可表示的精度范围内。对于数字 N,Epsilon = N / 10E+14

    System.Double.EpsilonDouble 类型的最小可表示正值。对于我们的目的而言,它小了。阅读Microsoft's advice on equality testing

    【讨论】:

    • 快速说明(但并不矛盾) - 如果您在 .NET 中使用 System.Decimal 类型,请注意它仍然是浮点类型。它是一个浮点小数点,但仍然是一个浮点数。哦,还要提防 System.Double.Epsilon,因为它不是您所期望的 :)
    【解决方案6】:

    我以前遇到过这种情况 (on my blog) - 我认为令人惊讶的往往是“非理性”数字是不同的。

    这里的“非理性”是指无法以这种格式准确表示的事实。真正的无理数(如 π - pi)根本无法准确表示。

    大多数人都熟悉 1/3 不能以十进制计算:0.3333333333333...

    奇怪的是 1.1 在浮点数中不起作用。人们期望十进制值可以在浮点数中工作,因为他们对它们的看法:

    1.1 是 11 x 10^-1

    实际上它们在 base-2 中时

    1.1 是 154811237190861 x 2^-47

    你无法避免,你只需要习惯一些浮点数是“非理性”的事实,就像 1/3 一样。

    【讨论】:

    • 基思,实际上你的例子都不是不合理的。 Sqrt(2) 是无理数,PI 是无理数,但根据定义,任何整数除以一个整数都是有理数。
    • 你说的很对——因此是单引号。在数学理论中,这些是有理数,它们只是无法在所使用的存储机制中表达。
    【解决方案7】:

    避免这种情况的一种方法是使用一个库,该库使用另一种表示十进制数的方法,例如BCD

    【讨论】:

    • 有比 BCD 更好的技术。
    • 如果能说出其中的一两个技巧就好了。
    【解决方案8】:

    在我看来,21.399999618530273 是 21.4 的 单精度(浮点)表示。看起来调试器正在从 double 向下转换为 float 某处。

    【讨论】:

      【解决方案9】:

      如果您使用 Java 并且需要准确性,请使用 BigDecimal 类进行浮点计算。它更慢但更安全。

      【讨论】:

        【解决方案10】:

        您无法避免这种情况,因为您使用的是具有固定字节数的浮点数。实数与其有限符号之间根本不可能存在同构。

        但大多数时候你可以简单地忽略它。 21.4==21.4 仍然是正确的,因为它仍然是具有相同错误的相同数字。但是 21.4f==21.4 可能不是真的,因为 float 和 double 的错误是不同的。

        如果您需要固定精度,也许您应该尝试定点数。甚至整数。例如,我经常使用 int(1000*x) 传递给调试分页器。

        【讨论】:

        • 实际上可能更喜欢 int(1000*x+.5) 以使 21.4 按预期显示。
        【解决方案11】:
        【解决方案12】:

        如果您觉得麻烦,您可以自定义调试期间某些值的显示方式。小心使用它:-)

        Enhancing Debugging with the Debugger Display Attributes

        【讨论】:

          【解决方案13】:

          参考General Decimal Arithmetic

          在比较浮点数时还要注意,请参阅this answer 了解更多信息。

          【讨论】:

            【解决方案14】:

            根据javadoc

            "如果数字运算符的至少一个操作数是双精度类型,则
            使用 64 位浮点运算进行运算,结果
            数值运算符是 double 类型的值。如果另一个操作数不是双精度,则它是
            首先通过数字提升(第 5.1.5 节)扩展为双精度类型(第 5.6 节)。”

            Here is the Source

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-05-31
              • 1970-01-01
              • 1970-01-01
              • 2020-12-05
              相关资源
              最近更新 更多