bigdecimal 并非银弹,而是通过字符串或整数构造、基于十进制算术来绕过二进制浮点缺陷的确定性工具;必须用 string 构造以避免精度污染,divide 需显式指定 scale 和 roundingmode,性能开销大且 equals 比较需谨慎。

BigDecimal 不是“解决”浮点数精度问题的银弹,而是绕过 float 和 double 二进制表示缺陷的确定性工具——它用字符串或整数构造,全程基于十进制算术,因此能精确表达 0.1、0.01 这类在二进制中无限循环的小数。
为什么 double 算钱会出错?
根本原因是 IEEE 754 双精度浮点数用二进制存储小数,而很多常用十进制小数(如 0.1)无法被精确表示:
System.out.println(0.1 + 0.2); // 输出 0.30000000000000004
这不是 Java 的 bug,所有遵循 IEEE 754 的语言都这样。金融计算要求“所见即所得”,double 的舍入不可控,BigDecimal 则把精度控制权交还给开发者。
必须用 String 构造 BigDecimal
用 double 构造 BigDecimal 会继承原始精度污染,等同于把错误“固化”:
立即学习“Java免费学习笔记(深入)”;
new BigDecimal(0.1) // 危险!实际是 new BigDecimal("0.1000000000000000055511151231257827021181583404541015625")正确做法只接受 String 或整数参数:
-
new BigDecimal("0.1")—— 精确按字面量解析 -
new BigDecimal("12345678901234567890")—— 支持超长整数 -
BigDecimal.valueOf(100L)—— 安全包装长整型(内部仍走字符串路径)
add、subtract、multiply、divide 的陷阱
add/subtract/multiply 默认保留全部精度,但 divide 必须显式指定 scale 和 RoundingMode,否则遇到除不尽直接抛 ArithmeticException:
bd1.divide(bd2) // 报错:Non-terminating decimal expansion
常见安全写法:
-
bd1.divide(bd2, 2, RoundingMode.HALF_UP)—— 保留 2 位小数,四舍五入(金融常用) -
bd1.divide(bd2, Math.max(bd1.scale(), bd2.scale()) + 2, RoundingMode.HALF_EVEN)—— 动态扩位,银行常用 - 避免用
setScale()替代divide()参数,它不改变除法逻辑,仅对结果截断
性能与可读性代价不能忽视
BigDecimal 是对象,运算涉及内存分配和算法开销,比原生类型慢 10–100 倍;同时代码更冗长:
total = total.add(price.multiply(quantity)); // vs total += price * quantity
这意味着:
- 非金融场景(如游戏坐标、物理模拟)别滥用
BigDecimal - 高频计算(如实时风控引擎)需评估吞吐瓶颈,有时用
long存“分”更高效 -
equals()比较要小心:new BigDecimal("1.0").equals(new BigDecimal("1"))返回false,应统一compareTo()或先stripTrailingZeros()
真正难的不是调用 API,而是决定在哪一层做精度截断、谁来负责舍入策略、以及如何让团队一致理解 scale 的业务含义——这些不会报编译错误,但会在上线后悄悄吃掉几厘钱。










