1. 引言
看起来,如果计算机“聪明”,那么 0.1 + 0.2 应该就是 0.3。但事实并非如此。让我们用一个简单的例子来看看。
double x = 0.1;
double y = 0.2;
double sum = x + y;
System.out.println(sum); // 程序会输出什么?
现在试着与 0.3 比较:
System.out.println(sum == 0.3); // 这里会是 true 还是 false?
如果你看到 false,不要惊讶!
原因在于内存中的数值表示
计算机以二进制表示数字。但并不是所有十进制小数都能表示为有限的二进制小数,就像 1/3 无法被十进制小数精确表示(0.333...)一样。比如,0.1 在二进制中是无限小数,为了存储不得不进行“舍入”。
通俗地说,double 有时“看起来”在精确存储你的数,但实际上只存储了一个非常接近的近似值。
2. 使用 double 进行运算会出现哪些“怪现象”?
我们来看一些实际示例。
示例 1:经典的 0.1 + 0.2 “魔法”
double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println(sum); // 0.30000000000000004
System.out.println(sum == 0.3); // false
计算机输出的不是 0.3,而是 0.30000000000000004。差异很小,但如果你处理的是金融之类的场景——这就很关键了。
示例 2:迭代相加
double result = 0;
for (int i = 0; i < 10; i++)
{
result += 0.1;
}
System.out.println(result); // 0.9999999999999999
本来想得到 1.0 —— 却得到略小的值。原因仍然是 double 内部的舍入。
为什么在真实任务中很重要
很多人会想:“差一点点有什么关系!”我们来看一个支付领域的例子。
假设你的网银要把 100 笔每笔 0.1 欧元的交易相加。如果你的程序在每次迭代中“丢失”十万分之一欧元,在银行这种规模下你就会“丢掉”真金白银。此时会计立刻就会来问你:“我们的钱去哪儿了?!”
3. 如何正确比较浮点数
由于 double 往往无法存下你期望的精确值,直接用 == 比较可能会误导。通常的做法是比较两个数之差的绝对值是否小于某个非常小的数(epsilon)。
带容差的比较示例
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;
if (Math.abs(a - b) < epsilon)
{
System.out.println("几乎相等!"); // 这样比较更安全
}
这里我们的意思是:“如果两个数的差小于百万分之一,就认为它们相等。”
说明: 函数 Math.abs(value) 返回传入数字的绝对值(模)。
4. double 的特殊值:Infinity、NaN、-Infinity
类型 double 不仅能表示普通数字,还能表示特殊值。它们出现在那些数学老师会严禁你去做的场景里。
无穷大(Infinity)
把 1 除以 0 会怎样?
double result = 1.0 / 0.0;
System.out.println(result); // Infinity
在 Java(以及许多语言)中,double 的除以 0 并不会抛出异常!相反,结果会变成一个特殊值“正无穷大”。
负无穷大(-Infinity)
如果用负数除以 0,就会得到负无穷大:
double result = -1.0 / 0.0;
System.out.println(result); // -Infinity
“非数”(NaN — Not a Number)
如果做一些很“奇怪”的事,例如对负数求平方根:
double result = Math.sqrt(-1);
System.out.println(result); // NaN
或者 0.0 / 0.0 的结果:
double result = 0.0 / 0.0;
System.out.println(result); // NaN
NaN 表示“在现实世界中不被视为数的任何东西”。
如何检查特殊值
Java 提供了用于检查这些特殊值的函数:
System.out.println(Double.isInfinite(result)); // 若为无穷大则为 true
System.out.println(Double.isNaN(result)); // 若为 NaN 则为 true
表格:double 对非常规操作的反应
| 操作 | 结果 | double 中存的是什么 |
|---|---|---|
|
Infinity | +∞ |
|
-Infinity | -∞ |
|
NaN | 非数 |
|
NaN | 非数 |
5. 处理浮点数时的常见错误
错误 №1:通过 == 比较小数
最常见的陷阱:试图用普通比较来判断两个计算得到的浮点数是否相等。由于舍入误差的累积,你几乎总会得到意外结果。务必使用带容差的比较(epsilon)。
错误 №2:计算中意外出现 NaN 和 Infinity
如果不控制除以零或对负数开方,程序中可能出现 NaN 或 Infinity,并“感染”后续计算。别忘了使用 Double.isNaN() 和 Double.isInfinite() 检查可疑值——这能帮助避免不愉快的惊喜。
错误 №3:把 NaN 用作“标记”
有些新手会用 NaN 作为“特殊”标记,比如“找不到就返回 NaN”。但请记住:NaN 很狡猾,用 == 比较不起作用!只能通过专用方法检查。
错误 №4:指望除以 0.0 会抛出异常
与整数除法不同,double 的除以 0.0 不会报错,而是返回 Infinity 或 NaN。这可能导致静默且难以察觉的 bug——确实有结果,但与预期不符。
GO TO FULL VERSION