【问题标题】:Rounding Errors?舍入错误?
【发布时间】:2010-10-31 21:32:55
【问题描述】:

在我的课程中,有人告诉我:

连续值在内存中近似表示,因此使用浮点数进行计算涉及舍入误差。这些是位模式的微小差异;因此,如果 ef 是浮点数,则测试 e==f 是不安全的。

参考 Java。

这是真的吗?我已经将比较语句与doubles 和floats 一起使用,并且从未遇到过舍入问题。我从来没有在教科书中读过类似的东西。肯定是虚拟机说明了这一点吗?

【问题讨论】:

    标签: java memory floating-accuracy


    【解决方案1】:

    这是真的。

    这是浮点值在内存中以有限位数表示的固有限制。

    例如,这个程序打印“false”:

    public class Main {
      public static void main(String[] args) {
        double a = 0.7;
        double b = 0.9;
        double x = a + 0.1;
        double y = b - 0.1;
        System.out.println(x == y);
      }
    }
    

    您通常不会与“==”进行精确比较,而是决定某种程度的精度并询问数字是否“足够接近”:

    System.out.println(Math.abs(x - y) < 0.0001);
    

    【讨论】:

    • 很好的解释。不过,您的最后一个代码示例可能应该使用 Math.abs(x - y) 而不是 x - y。
    • 由于直观的代码示例,这是我选择的答案。不错!
    【解决方案2】:

    这适用于 Java 和任何其他使用浮点的语言一样。它是硬件中浮点值表示的设计所固有的。

    更多关于浮点值的信息:

    What Every Computer Scientist Should Know About Floating-Point Arithmetic

    【讨论】:

    • 一点:如果两个计算完全相同,那么它们的结果值也将相同。当两个计算在数学上等价但不同时,就会出现问题。
    【解决方案3】:

    是的,以 2 为底数精确表示 0.1 与试图以 10 为底数精确表示 1/3 相同。

    【讨论】:

      【解决方案4】:

      这总是正确的。有些数字无法使用浮点表示准确表示。例如,考虑 pi。您将如何在有限的存储空间中表示具有无限位的数字?因此,在比较数字时,您应该检查它们之间的差异是否小于某个 epsilon。此外,还有几个类可以帮助您获得更高的准确性,例如 BigDecimal 和 BigInteger。

      【讨论】:

      • 是的,但是如果两个计算产生相同的浮点数,使用 e==f 会返回 true?
      • @Beau,如果 e 和 f 确实是相同的数字,则检查返回 true。但是有一些注意事项,例如,看似简单且数学上正确的比较,如 ((x * y) / y == x) 可能是错误的
      • 您已经成功地在有限存储中准确地表示 pi:通过使用它的名称。数字不仅仅是他们习惯的十进制表示。考虑 1/3:这也有无限位数(以 10 为底),但可以精确表示:作为有理数(或以 3 为底)。即使它不能完全表示为以 2 或 10 为底的浮点数。
      【解决方案5】:

      没错。请注意,Java 与它无关,问题是 ANY 语言中的浮点数学固有的问题。

      您通常可以解决课堂级别的问题,但它在现实世界中行不通。有时它在教室里行不通。

      很久以前在学校发生的一件事。入门课的老师布置了一道期末考试题,这对许多成绩较好的学生来说确实很棘手——它不起作用,他们也不知道为什么。 (我作为实验室助理看到这个,我不在课堂上。)最后一些人开始向我寻求帮助,一些探索揭示了问题:他们从未被教导过浮点数学固有的不准确性。

      现在,有两种基本方法来解决这个问题,一种是蛮力方法(偶然在这种情况下有效,因为它每次都会犯相同的错误)和一种更优雅的方法(它会犯不同的错误但不起作用。 ) 任何尝试过优雅方法的人都会在不知道为什么的情况下碰壁。我帮助了他们中的许多人,并坚持在评论中解释原因并在他有问题时与我联系。

      当然,下学期我从他那里听说了这件事,我基本上用一个简单的小程序让整个系都大吃一惊:

      10 X = 3000000
      20 X = X + 1
      30 If X < X + 1 goto 20
      40 Print "X = X + 1"
      

      尽管系里的每个老师都这么想,这个终止。 300万种子只是为了让它更快地终止。 (如果你不懂基本:这里没有噱头,只是耗尽了浮点数的精度。)

      【讨论】:

        【解决方案6】:

        是的,正如其他答案所说。我想补充一点,我推荐你这篇关于浮点精度的文章:Visualizing floats

        【讨论】:

          【解决方案7】:

          当然是真的。想想看。任何数字都必须用二进制表示。

          图片:“1000”为0.5或1/2,即2**-1。那么“0100”就是 0.25 或 1/4。你可以看到我要去哪里。

          你可以用这种方式表示多少个数字? 2**4。添加更多位会复制可用空间,但它永远不会是无限的。 1/3 或 1/10,对于 1/n,任何不是 2 的倍数的数字都不能真正表示。

          1/3 可以是“0101”(0.3125)或“0110”(0.375)。如果将其乘以 3,则任何一个值都不会是 1。当然,您可以添加特殊规则。假设您“当您添加 3 次 '0101' 时,将其设为 1”......这种方法从长远来看是行不通的。你可以抓到一些,但是 1/6 乘以 2 怎么样?

          这不是二进制表示的问题,任何有限的表示都有你无法表示的数字,它们毕竟是无限的。

          【讨论】:

            【解决方案8】:

            大多数 CPU(和计算机语言)使用 IEEE 754 浮点运算。使用这种表示法,有一些十进制数在这种表示法中没有精确的表示,例如0.1。因此,如果您将 1 除以 10,您将不会得到准确的结果。当连续执行多个计算时,错误会相加。在 python 中尝试以下示例:

            >>> 0.1
            0.10000000000000001
            >>> 0.1 / 7 * 10 * 7 == 1
            False
            

            这并不是你所期望的数学。

            顺便说一句: 关于浮点数的一个常见误解是,结果不精确,无法安全地进行比较。仅当您真正使用数字的分数时,这才是正确的。如果您所有的数学都在整数域中,则双精度和浮点数与整数完全相同,也可以安全地进行比较。例如,它们可以安全地用作循环计数器。

            【讨论】:

            • 对于整数域中的浮点数,我不同意您的 cmets。简单例子1:float f2 = 20000000; if (f2 == ++f2) { ;//oops } 示例 2:这个循环何时终止完全不明显:float f = 0; while (true) { if (f == ++f) { break; } }
            • 只要保持在可以表示为整数的范围内,就可以安全地比较它们。有关失败的示例,请参阅我的消息。
            • @Ben:保持在范围内也是整数的问题:int i = 0; while (i
            【解决方案9】:

            是的,Java 也使用floating point 算术。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-04-03
              • 2018-03-05
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多