Java 大厂面试高频坑:double 为什么不能精确保存 0.1?
在 Java 中,double 是非常常用的小数类型。很多初学者会认为:
double d = 0.1;
这句代码的意思就是把 0.1 精确保存到了变量 d 中。
但严格来说,double 不能精确保存 0.1,它保存的只是一个非常接近 0.1 的近似值。
这也是为什么在 Java 中经常会看到这样的现象:
public class Test {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
}
}
输出结果可能是:
0.30000000000000004
而不是我们数学上认为的:
0.3
那么,为什么会这样呢?
一、double 是二进制浮点数
Java 中的 double 是双精度浮点数,底层遵循 IEEE 754 浮点数标准,使用二进制形式保存小数。
也就是说,计算机不是直接用十进制保存 0.1、0.2、0.3,而是先把它们转换成二进制,再保存到内存中。
整数通常比较好理解,例如:
十进制 10 = 二进制 1010
但是小数就不一定能被二进制精确表示。
二、0.1 转成二进制是无限循环小数
我们在十进制中经常遇到除不尽的情况,例如:
1 / 3 = 0.333333333...
这里的 0.333333... 是无限循环小数。因为十进制无法用有限位数精确表示三分之一。
类似地,十进制的 0.1 转成二进制时,也会变成无限循环小数,大概是:
0.1(十进制) = 0.0001100110011001100110011...(二进制)
后面的 0011 会不断循环下去。
但是 double 的存储空间是有限的。double 一般占 64 位,不可能保存无限长的二进制小数。所以它只能截断或四舍五入,保存一个近似值。
因此:
double d = 0.1;
这句代码并不是把精确的 0.1 存进去,而是把最接近 0.1 的某个二进制浮点数存进去。
三、为什么 0.1 + 0.2 不是 0.3?
因为 0.1 和 0.2 本身都不能被 double 精确表示。
可以理解为:
0.1 实际保存的是接近 0.1 的近似值
0.2 实际保存的是接近 0.2 的近似值
0.3 实际保存的是接近 0.3 的近似值
当执行:
System.out.println(0.1 + 0.2);
计算机拿到的并不是数学意义上完全精确的 0.1 和 0.2,而是两个近似值。两个近似值相加后,结果可能就会出现非常小的误差。
所以输出结果可能是:
0.30000000000000004
这并不是 Java 算错了,而是浮点数表示方式决定的。
再看一个例子:
public class Test {
public static void main(String[] args) {
System.out.println(0.1 + 0.2 == 0.3);
}
}
输出结果通常是:
false
原因是:
0.1 + 0.2 得到的近似值
和
0.3 保存的近似值
并不完全相等
所以,在 Java 中不要直接用 == 比较两个浮点数是否相等。
四、那为什么 System.out.println(0.1) 看起来正常?
你可能会问:
System.out.println(0.1);
输出明明是:
0.1
这不是说明 double 可以保存 0.1 吗?
其实不是。
这是因为 Java 在输出 double 时,会进行格式化处理。它会选择一个最简洁、最接近原值的十进制显示形式。所以虽然底层保存的是近似值,但打印时可能仍然显示为 0.1。
换句话说:
显示出来是 0.1
不代表底层精确保存了 0.1
只有在参与计算、比较或者格式化到更多小数位时,误差才更容易暴露出来。
五、浮点数应该怎么比较?
既然浮点数不能直接用 == 比较,那应该怎么判断两个浮点数是否相等呢?
常见做法是设置一个允许误差范围,也叫 epsilon。
例如:
public class Test {
public static void main(String[] args) {
double a = 0.1 + 0.2;
double b = 0.3;
if (Math.abs(a - b) < 1e-10) {
System.out.println("近似相等");
} else {
System.out.println("不相等");
}
}
}
这里的意思是:只要两个数的差值非常小,就认为它们相等。
这适合普通科学计算、统计计算、图形计算等场景。
六、金额计算不要用 double
如果是金额、余额、订单价格、支付结算等场景,不建议使用 double 或 float。
例如:
double price = 0.1;
double total = price * 3;
System.out.println(total);
结果可能不是你期望的精确金额。
在金融、支付、电商等业务中,金额必须精确,不能出现浮点误差。因此应该使用 BigDecimal。
例如:
import java.math.BigDecimal;
public class Test {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
}
}
输出:
0.3
注意,创建 BigDecimal 时推荐使用字符串:
new BigDecimal("0.1")
不推荐使用:
new BigDecimal(0.1)
因为 0.1 作为 double 传进去时,已经是近似值了。
七、面试怎么回答?
如果面试官问:double 能不能精确保存 0.1?
可以这样回答:
严格来说,double 不能精确保存 0.1。因为 double 底层使用二进制浮点数表示小数,而十进制的 0.1 转成二进制是无限循环小数,double 的位数有限,只能保存一个近似值。所以浮点数计算可能出现精度误差,比如 0.1 + 0.2 可能得到 0.30000000000000004。普通浮点比较一般使用误差范围判断,涉及金额计算时应该使用 BigDecimal,并且推荐用字符串构造 BigDecimal。
八、总结
double 不能精确保存 0.1,根本原因是:十进制小数 0.1 转成二进制后是无限循环小数,而 double 的存储位数有限,只能保存近似值。
因此,在 Java 中需要注意:
1. double 保存小数可能有精度误差
2. 0.1 + 0.2 不一定等于 0.3
3. 不建议直接用 == 比较浮点数
4. 普通计算可以使用误差范围比较
5. 金额计算应该使用 BigDecimal
6. BigDecimal 推荐使用字符串构造
一句话总结:
double 适合近似计算,不适合精确金额计算。
更多推荐




所有评论(0)