BigDecimal 是金额计算的唯一合理选择,因其用整数+标度方式存储,避免浮点误差,确保业务可预期、审计可追溯;构造须用字符串或 valueOf(),运算需显式指定舍入模式,比较必须用 compareTo()。

为什么 BigDecimal 是金额计算的唯一合理选择
因为 float 和 double 在二进制下无法精确表示大多数十进制小数,比如 0.1 + 0.2 得到的是 0.30000000000000004,而金融系统要求“算多少就是多少”。BigDecimal 用整数+标度(scale)的方式存储数值,完全规避了浮点误差,是 Java 中唯一能保证**业务上可预期、审计上可追溯**的精度载体。
BigDecimal 构造时千万别用 double 参数
这是最常踩的坑:用 new BigDecimal(0.1) 看似写得对,实际传入的是已经失真的 double 值,结果仍是 0.1000000000000000055511151231257827021181583404541015625。
BigDecimal a = new BigDecimal(0.1); // ❌ 危险!
BigDecimal b = new BigDecimal("0.1"); // ✅ 正确:字符串构造
BigDecimal c = BigDecimal.valueOf(0.1); // ✅ 安全:内部用字符串转换
- 永远优先用
String构造或BigDecimal.valueOf() -
valueOf()底层调用的是Double.toString(),虽比直接传double好,但仍有隐式转换风险,关键金额建议坚持用字符串 - 从数据库读取时,如果字段是
DECIMAL,JDBC 驱动通常返回BigDecimal,无需手动转换;若用getDouble()再转,就又掉回坑里
加减乘除必须显式指定 RoundingMode
BigDecimal 的 divide() 方法不指定舍入模式会直接抛 ArithmeticException(如 1 ÷ 3 无限循环),而 add()/subtract()/multiply() 虽不强制,但 scale 不一致时结果 scale 会变,可能引发后续比较或入库异常。
BigDecimal x = new BigDecimal("10.25");
BigDecimal y = new BigDecimal("3");
BigDecimal z = x.divide(y, 2, RoundingMode.HALF_UP); // ✅ 明确:保留2位,四舍五入
- 金融场景几乎只用
RoundingMode.HALF_UP(银行家舍入是HALF_EVEN,但国内计息/收银普遍用传统四舍五入) - 除法前务必确认分母非零,
divide()不做空值检查 - 乘法后 scale 是两操作数 scale 之和,如
"1.23" × "4.5"→ scale=3,若需统一为 2 位,得额外调用setScale(2, RoundingMode.HALF_UP)
比较大小别用 == 或 equals()
equals() 在 BigDecimal 中同时比较值和 scale,new BigDecimal("1.0").equals(new BigDecimal("1.00")) 返回 false——这在金额校验中极易导致逻辑错误。而 == 比的是引用,更不可靠。
立即学习“Java免费学习笔记(深入)”;
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
System.out.println(a.equals(b)); // false
System.out.println(a.compareTo(b) == 0); // true ✅
- 一律用
compareTo()判断相等、大小关系 -
compareTo()返回 -1 / 0 / 1,不抛异常,适合 if 判断和排序 - 存入数据库前,可用
stripTrailingZeros().toPlainString()统一格式,避免因 scale 不同导致重复记录
真正难的不是写对一行 BigDecimal 代码,而是整个调用链——从 HTTP 请求解析、DTO 转换、Service 计算到 DB 存储——每一步都保持 scale 意识和构造方式一致。一个 Double.parseDouble() 混进去,前面所有严谨操作就白做了。










