Mr.Mou @ ShiShi AP Center

AP CSA 1.5 拓展说明:double 与舍入误差

Roundoff Error 是什么?

在 AP CSA 里,roundoff error 指的是:

Java 使用 double 存储和计算小数时,因为有些小数不能被电脑精确表示,所以结果可能出现非常小的误差。

它不是随机错误。 同一段 Java 代码重复运行,通常会得到同样的结果。 问题在于:这个结果可能不是数学上的“精确值”,而是一个非常接近的近似值。


为什么小数会不精确?

人类平时使用的是 十进制,电脑底层使用的是 二进制

二进制的小数位表示的是:

1/2, 1/4, 1/8, 1/16, 1/32 ...

所以像下面这些数,二进制很容易精确表示:

0.5    // 1/2
0.25   // 1/4
0.125  // 1/8
0.75   // 1/2 + 1/4

但是像下面这些数,虽然在十进制里看起来很简单:

0.1
0.2
0.3
1.1
1.2

它们在二进制里可能是无限循环小数。

例如:

0.1₁₀ = 0.00011001100110011...₂

后面的 0011 会一直重复下去。

Java 的 double 不可能存无限长的小数,所以只能存一个非常接近 0.1 的值。这个近似值带来的微小误差,就是 roundoff error


例子:为什么 0.1 + 0.2 不是刚好 0.3

System.out.println(0.1 + 0.2);

你可能以为输出是:

0.3

但 Java 可能输出:

0.30000000000000004

原因是 Java 并不是在计算:

精确的 0.1 + 精确的 0.2

而是在计算:

非常接近 0.1 的值 + 非常接近 0.2 的值

所以最后结果也可能只是非常接近 0.3,但不完全等于 0.3


为什么 (a + b) * (a - b) 可能不等于 a * a - b * b

数学上:

(a + b)(a - b) = a² - b²

这个恒等式当然是正确的。

但是在 Java 里,如果 abdouble,情况可能不同。

例如:

double a = 1.1;
double b = 1.2;

if ((a + b) * (a - b) != (a * a) - (b * b))
{
    System.out.println("Mathematical error!");
}

数学上左右两边应该相等。

但是 Java 中:

(a + b) * (a - b)

和:

(a * a) - (b * b)

计算顺序不同。

左边先算:

a + b
a - b

再相乘。

右边先算:

a * a
b * b

再相减。

由于 1.11.2 本身就不能被 double 精确存储,不同的计算过程会产生不同的微小误差。

结果可能类似这样:

(a + b) * (a - b)   -> -0.22999999999999968
(a * a) - (b * b)   -> -0.22999999999999976

它们非常接近,但不是完全相等。

所以这个判断可能成立:

left != right

于是程序会输出:

Mathematical error!

如果只有一个小数,会不会也有 roundoff error?

会,只要这个小数不能被二进制精确表示,就可能出现 roundoff error

例如:

double a = 1.1;
int b = 2;

System.out.println((a + b) * (a - b));
System.out.println((a * a) - (b * b));

虽然 b 是整数,但是参与 double 运算时会被当作 2.0 来计算。

真正的问题来自:

double a = 1.1;

因为 1.1 不能被二进制精确表示。

所以即使表达式里只有一个不精确的小数,也可能导致最终结果出现微小误差。

不过要注意:不是所有小数都会出问题。比如 1.50.25 这类可以用二进制精确表示的小数,通常不会造成这种问题。


AP CSA 重点总结

double 适合表示和计算小数,但它表示的是近似值,不是所有小数都能被精确存储。

所以不要直接用 ==!= 判断两个 double 计算结果是否完全相等。

不推荐:

if (x == y)

更安全的写法是判断它们是否足够接近:

if (Math.abs(x - y) < 0.000001)
{
    System.out.println("Close enough");
}

一句话总结:

Roundoff error 就是 Java 用 double 存储和计算小数时,由于二进制无法精确表示某些十进制小数而产生的微小误差。