用 double/float 计算金钱会出错,因其二进制无法精确表示 0.1 等十进制小数,导致截断误差;应始终使用 bigdecimal("0.1") 而非 bigdecimal(0.1),运算用 add/subtract/multiply/divide(除法须指定精度与舍入模式),比较用 compareto() 而非 equals()。

为什么 double 和 float 在钱的计算里会出错
因为它们底层用二进制表示小数,而像 0.1、0.2 这类十进制小数在二进制中是无限循环小数,必须截断——这就导致加减后出现 0.30000000000000004 这种结果。商业系统里“1分钱差错”就是 bug,不是精度问题,是逻辑错误。
根本原因不是运算本身错了,而是你用错了类型:double 适合科学计算,不适用于精确十进制运算。
必须用 BigDecimal(String) 构造,别用 double 参数构造
这是最常踩的坑:用 new BigDecimal(0.1) 看似方便,实则把 double 的二进制误差直接带进去了。哪怕你写的是 0.1,JVM 已经把它存成近似值,再转 BigDecimal 只是“精确地保存了错误”。
- ✅ 正确:
new BigDecimal("0.1")、new BigDecimal("199.99") - ❌ 错误:
new BigDecimal(0.1)、new BigDecimal(199.99) - ⚠️ 补救方案:如果只能拿到
double值(比如从 JSON 解析来),先转成字符串再构造:new BigDecimal(Double.toString(d)),但更推荐源头就用字符串传
add、subtract、multiply、divide 的调用细节不能省
BigDecimal 是不可变对象,所有运算都返回新实例,原对象不变;而且除法必须指定精度和舍入模式,否则遇到除不尽直接抛 ArithmeticException。
-
add/subtract/multiply可直接用,但注意参数必须是BigDecimal,别混用int或long(会自动装箱,但语义模糊) -
divide必须带两个参数:scale(小数位数)和RoundingMode,例如:price.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP) - 乘法默认精度是两操作数 scale 之和,如
"1.23" × "4.5"得"5.535"(scale=3),若要保留两位小数,得链式调用.setScale(2, RoundingMode.HALF_UP)
比较大小别用 == 或 equals(),要用 compareTo()
equals() 会同时比较值和 scale,new BigDecimal("1.0").equals(new BigDecimal("1.00")) 返回 false——这在金额相等性判断中极易引发逻辑漏洞。而 compareTo() 只比数值大小,返回 -1/0/1,语义清晰可靠。
- ✅ 判断相等:
a.compareTo(b) == 0 - ✅ 判断大于:
a.compareTo(b) > 0 - ❌ 避免:
a.equals(b)、a == b(引用比较,永远 false) - ⚠️ 注意:
compareTo()对null抛空指针,调用前务必判空
真正难的不是写对几行 BigDecimal 代码,而是让整个调用链——从 HTTP 请求解析、数据库读写、到日志输出——全程守住字符串入口、避免隐式转换。一旦某个环节悄悄 toString() 再 parseDouble(),前面所有努力就白做了。










