导读:本期聚焦于小伙伴创作的《怎么处理 double 类型在进行减法运算时出现的 0.0000000000001 误差》,敬请观看详情。明明两个小数相减,结果却多出了 0.0000000000001 这样细碎的偏差,这种 double 减法误差常让金额核对和判断逻辑出问题。其根源在于 IEEE 754 双精度浮点用二进制近似表示多数十进制小数,减法放大了末位舍入差。直接比较相等或累加都可能出错。可用四舍五入限定小数位、改用整数分单位记账,或借助 BigDecimal 指定精度与舍入模式彻底规避,下文将说明具体写法与适用场景。

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

怎么处理 double 类型在进行减法运算时出现的 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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。