必须用字符串初始化bigdecimal,因double构造会继承浮点误差导致精度丢失;正确方式是new bigdecimal("0.1")或bigdecimal.valueof(0.1),避免0.1+0.2≠0.3等计算错误。

直接用 double 做金额或高精度计算,结果常常“看起来对、其实错”。这不是 Java 的 bug,而是二进制浮点数的固有缺陷——它根本无法精确表示大多数十进制小数,比如 0.1、0.01、0.35。一旦参与运算,误差会累积,最终导致业务逻辑出错,比如账不平、优惠算多、分润少付。
double 为什么算不准?
因为 double 按 IEEE 754 标准用 64 位二进制存储,而像 0.1 这样的十进制小数,在二进制中是无限循环小数(类似十进制里的 1/3 = 0.333…)。系统只能截断保存近似值:
-
System.out.println(0.1 + 0.2);→ 输出0.30000000000000004 -
System.out.println(0.03 - 0.02);→ 输出0.009999999999999998
BigDecimal 怎么解决这个问题?
它不依赖二进制浮点规则,而是把数字拆成两部分存:一个整数(用 BigInteger)+ 一个小数点位置(scale)。例如 "12.34" 实际存为整数 1234 和 scale 2,所有运算都在整数层面进行,天然避免精度丢失。
- 加减乘除都返回新对象,原值不变,线程安全
- 支持自定义舍入方式(如
RoundingMode.HALF_UP),应对除不尽场景 - 标度(小数位数)可显式控制,适合金额统一保留两位
初始化 BigDecimal 的关键避坑点
错的初始化方式会让后续所有计算“从起点就失真”:
- ❌
new BigDecimal(0.1)—— 先用double表示 0.1 就已失真,再包装也救不回来 - ✅
new BigDecimal("0.1")—— 字符串无转换损耗,最稳妥 - ✅
BigDecimal.valueOf(0.1)—— 内部转字符串再构造,日常够用且写法简洁
实际用法中的几个硬性习惯
光创建对还不够,比较和运算也要守规矩:
- 比较大小别用
equals(),用compareTo()—— 它只比数值,不比 scale - 除法必须指定精度和舍入模式:
a.divide(b, 2, RoundingMode.HALF_UP) - 加减乘建议统一 scale,比如金额运算前先调用
setScale(2, RoundingMode.HALF_UP)











