在 Java 等语言里,double 属于 IEEE 754 双精度浮点类型,用 64 位存储一个数,其中尾数有限。像 0.1 这样普通的十进制小数,换成二进制却是无限循环小数,只能被截断保存,于是本身就带了极小误差。当两个带误差的 double 做减法,比如 1.0 减 0.9,理论结果是 0.1,实际可能得到 0.09999999999999998,再叠加运算就容易出现 0.0000000000001 级别的尾巴。

为什么减法会暴露误差
浮点数的误差不是减法才产生的,而是赋值那一瞬间就存在了。例如 0.1 和 0.2 在内存里都不是精确值,只是非常接近。加法、乘法有时还能在输出时被默认格式化隐藏,但减法会让两个相近大数消掉高位、把低位残差相对放大,于是 1.1 减 1.0 可能出来 0.10000000000000009。这种误差在循环累加或边界判断时尤其危险。
我们可以用一段简单代码观察现象。下面在 Java 中打印两个 double 相减的结果:
public class DoubleSub {
public static void main(String[] args) {
double a = 1.1;
double b = 1.0;
double c = a - b;
// 直接打印,可能看到 0.10000000000000009
System.out.println(c);
// 用格式化只保留两位小数
System.out.printf("%.2f%n", c);
}
}
从输出能看出,原始 c 带着长尾巴,而格式化后看似正常。但若写 if (c == 0.1) 就可能失败,因为右侧 0.1 也有自己的误差,两边并不严格相等。
方案一:比较时用误差容忍
如果业务只关心“差不多就行”,可以引入一个极小的 epsilon 来比较。思路是判断两个 double 的差的绝对值是否小于允许范围,而不是用等号。这样能避开末位噪声,又不必改数据类型。
示例代码如下:
public class EpsilonCompare {
private static final double EPS = 1e-9;
public static boolean nearlyEqual(double x, double y) {
return Math.abs(x - y) < EPS;
}
public static void main(String[] args) {
double result = 1.1 - 1.0;
// 用容忍误差方式判断,输出 true
System.out.println(nearlyEqual(result, 0.1));
}
}
这种写法的好处是轻量,不改既有计算逻辑。缺点是 EPS 选多大依赖场景,太小仍可能失败,太大又可能掩盖真实差异;且它只解决“比较”,没解决“存储值本身不准”。
方案二:用 BigDecimal 精确计算
对金额、利率等不能容忍误差的场景,Java 提供了 BigDecimal。它用十进制刻度保存数字,可指定精度和舍入规则。注意必须用字符串构造,若用 new BigDecimal(0.1) 仍会带入 double 的误差。
下面演示用 BigDecimal 做减法并控制精度:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BdSub {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("1.1");
BigDecimal b = new BigDecimal("1.0");
// 减法,结果为 0.1 精确值
BigDecimal c = a.subtract(b);
System.out.println(c);
// 若从 double 来,先转字符串或用 valueOf
BigDecimal d = BigDecimal.valueOf(1.1)
.subtract(BigDecimal.valueOf(1.0))
.setScale(2, RoundingMode.HALF_UP);
System.out.println(d);
}
}
BigDecimal 的缺点是运算比 double 慢,对象也占更多内存,不适合高频物理仿真。但在交易、账单里,它是最稳妥的选择。配合 setScale 与 RoundingMode,可明确截断或四舍五入策略,避免隐式行为。
方案三:改用整数最小单位
另一个实用思路是彻底不存小数。例如金额以“分”为单位用 long 保存,1.10 元存成 110。所有加减都是整数运算,绝无浮点误差,展示时再除以 100。此法在电商、支付系统很常见。
示例:
public class IntMoney {
public static void main(String[] args) {
long price = 110L; // 1.10 元
long cost = 100L; // 1.00 元
long profit = price - cost; // 10 分,即 0.10 元
System.out.println("利润(分): " + profit);
System.out.println("利润(元): " + (profit / 100.0));
}
}
这种做法性能高、逻辑简单,但要小心除法和跨单位换算。如果业务本身就有复杂小数且频繁变精度,还是 BigDecimal 更灵活。
总结建议
遇到 double 减法出现 0.0000000000001 误差,先想清楚场景:仅做显示或粗略比较,用 epsilon 或格式化即可;涉及钱、合同数字,优先 BigDecimal 或整数分单位。理解 IEEE 754 的近似本质,就能在编码时主动避开坑,而不是事后怀疑语言有 bug。
double精度误差浮点数运算BigDecimal修改时间:2026-08-10 23:03:35