舍入误差?

30
在我的课程中,我被告知:
连续值在内存中以近似方式表示,因此使用浮点数进行计算涉及到舍入误差。这些是位模式中微小的差异;因此,如果e和f是浮点数,则测试e==f是不安全的。
这是否正确?我已经使用doublefloat的比较语句,并从未遇到过舍入问题。我从来没有在教科书上读到过类似的东西。虚拟机肯定会考虑到这一点吧?
9个回答

64

它是正确的。

由于浮点数值在内存中以有限数量的位表示的固有局限性,这是不可避免的。

例如,此程序会输出“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);

3
好的解释。您最后的代码示例可能应该使用 Math.abs(x - y) 而不是 x - y。 - markusk
因为这个直观的代码示例,这是我首选的答案。不错! - Humphrey Bogart

29

3
一点提示:如果两个计算完全相同,则它们的结果值也将相同。但当两个计算在数学上等价但不同的时候,问题就出现了。 - dfrankow

9

是的,在二进制中精确表示0.1就像在十进制中精确表示1/3一样困难。


4

这是一条永恒的真理。有些数字不能用浮点数精确地表示。例如,考虑圆周率。如何在有限的存储空间内表示一个具有无限位数的数字呢?因此,在比较数字时,应检查它们之间的差异是否小于某个 epsilon。此外,还有几个类可以帮助您实现更高的精度,例如 BigDecimal 和 BigInteger。


是的,但如果两个计算得出相同的浮点数,使用 e==f 将返回 true 吗? - Humphrey Bogart
1
@Beau,如果e和f确实是相同的数字,则检查返回true。但是有一些注意事项,例如一个看似简单且在数学上正确的比较,如((x * y) / y == x),可能会是false。 - Ben Schwehn
你已经成功地通过使用π的名称在有限存储空间中准确地表示了它。 数字不仅仅是它们通常的十进制表示。考虑1/3:这也有无限的数字(在基数10中),但可以准确地表示为有理数(或在基数3中)。即使它不能被表示为基数2或10的浮点数。 - Adrian Pronk

4

这是正确的。请注意,Java与此无关,问题根源在于任何语言中的浮点数学。

你经常可以在课堂级别的问题中解决它,但在现实世界中不起作用。有时在课堂上也行不通。

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

现在,对于这个问题有两种基本方法,一种是暴力方法(在这种情况下偶然起作用,因为它每次都会犯同样的错误),另一种是更优雅的方法(会产生不同的错误,而且不起作用)。任何尝试优雅方法的人都会撞上一堵墙,却不知道为什么。我帮了他们一堆人,并加了一条评论解释原因,如果他有问题可以联系我。

当然,下个学期我从他那里听到了这件事,我基本上用一个简单的小程序打败了整个部门:

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

尽管该部门的每位教师都认为不可能,但这个程序一定会终止。3百万种子只是为了加快终止速度。(如果你不懂基础知识:这里没有任何花招,只是耗尽浮点数的精度。)


2

是的,就像其他答案所说的一样。我想补充一下,我建议您阅读有关浮点数精度的文章:可视化浮点数


2
当然是真的。想一想,任何数字都必须用二进制表示。
以“1000”为例,可以表示为0.5或1/2,即2 ** -1。然后,“0100”是0.25或1/4。你可以看出我要说什么。
你可以用这种方式表示多少个数字?2**4。添加更多位会复制可用空间,但它永远不是无限的。对于任何不是2的倍数的数字,例如1/3或1/10,实际上都无法真正表示。
1/3可以表示为“0101”(0.3125)或“0110”(0.375)。如果将其乘以3,任何一个值都不会是1。当然,你可以添加特殊规则。比如说,“当你加3次‘0101’时,将其变为1”......这种方法在长期内行不通。你可能能够抓住一些问题,但是对于1/6乘以2怎么办呢?
这不是二进制表示的问题,任何有限表示都有无法表示的数字,毕竟它们是无限的。

2

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

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

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

顺便说一下: 关于浮点数,一个常见的误解是,结果不精确,不能安全地进行比较。如果你真的只使用整数,那么这个误解是错误的。双精度和单精度浮点数与整数完全相同,也可以安全地进行比较。它们可以安全地用作循环计数器。


我不同意你关于浮点数在整数领域的评论。简单示例1: float f2 = 20000000; if (f2 == ++f2) { ;//oops }示例2:当这个循环会终止是完全不明显的:float f = 0; while (true) { if (f == ++f) { break; } } - Ben Schwehn
1
只要你在可以表示为整数的范围内比较它们,它们就可以安全地进行比较。请参考我的消息,了解其中失败的示例。 - Loren Pechtel
@Ben:使用整数时保持在范围内也是一个问题: int i = 0; while (i < 2147483648) {}但我同意,浮点数溢出存在更加微妙的问题。 - Nikolai Ruhe

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接