使用 new BigDecimal(double) 会继承 double 的二进制精度误差,正确做法是用字符串构造(如 new BigDecimal("0.1"))或 BigDecimal.valueOf(d),并全程避免 double 中间层、乘除指定舍入规则、序列化时保持精度。

直接用 new BigDecimal(double) 会把 double 已有的二进制误差原样“照搬”进来,比如 new BigDecimal(0.1) 得到的不是 0.1,而是 0.10000000000000000555...。这不是 BigDecimal 的 bug,而是 double 本身在内存里就存不准——BigDecimal 只是忠实地记录了那个错误值。
构造阶段:杜绝 double 直接入参
这是最核心、最容易踩的坑。只要源头用了 new BigDecimal(d),后面所有运算都建立在错误基础上。
- ✅ 正确做法:用字符串构造 ——
new BigDecimal("0.1"),十进制字面量被完整保留 - ✅ 推荐替代:用
BigDecimal.valueOf(d),它内部调用Double.toString(d)获取最短可读十进制表示(如0.1而非近似值) - ❌ 绝对禁止:
new BigDecimal(0.1)、new BigDecimal(someDouble) - ⚠️ 注意:
String.valueOf(d)不可靠,可能输出科学计数法(如"1.23E5"),应改用Double.toString(d)
数据来源阶段:绕过 double 中间层
前端传参、数据库读写、配置文件解析等环节,尽量不让 double 类型出现。
- 前端传金额时,应传字符串(如
"19.99"),后端直接new BigDecimal(param) - 数据库字段必须定义为
DECIMAL(18,2)等定点类型,JDBC 查询时用rs.getBigDecimal("amount"),而非rs.getDouble()再转 - 写入时用
ps.setBigDecimal(1, bd),避免反向污染
运算阶段:乘除必须指定舍入规则
加减法天然保精度(标度一致时),但乘除会改变小数位数,不控制就会失控或抛异常。
立即学习“Java免费学习笔记(深入)”;
-
add()/subtract():可直接调用,结果标度取较大者 -
multiply():建议用双参数版本,如a.multiply(b, MathContext.DECIMAL64)或自定义精度 -
divide():必须指定标度和舍入模式,例如a.divide(b, 2, RoundingMode.HALF_EVEN)(银行家舍入,金融常用) - 不指定时,
divide()遇除不尽直接抛ArithmeticException
序列化与显示阶段:保持精度不被破坏
对象转 JSON 或前端展示时,若处理不当,仍可能退化回 double 表示。
- 使用 Jackson 时,启用
WRITE_BIGDECIMAL_AS_PLAIN,避免输出成科学计数法或自动转 double - 前端接收时,不要用 JavaScript 的
parseFloat解析 BigDecimal 字符串,应原样展示或用支持高精度的库(如big.js) - 日志打印或调试时,用
toString()而非doubleValue(),后者会再次引入误差


















