java金额计算精度问题源于float/double二进制表示缺陷,bigdecimal通过字符串建模十进制并用整数运算规避;关键在构造(用string或valueof)、数据库直通decimal、乘除显式控标度与舍入、序列化输出纯字符串。

Java 中金额计算出现 0.1 + 0.2 = 0.30000000000000004 这类问题,不是代码写错,而是 float/double 本身无法精确表示十进制小数。BigDecimal 不是“修复”浮点数,而是彻底绕过它——用字符串建模十进制,再用整数逻辑运算。关键不在“用了没”,而在“怎么用才不丢精度”。
构造阶段:守住第一道防线
90% 的精度问题发生在创建 BigDecimal 的那一刻。double 值一旦生成,误差已固化,后续任何转换都只是在错误基础上“精确记录”。
- ✅ 推荐:前端传字符串 "19.99" →
new BigDecimal("19.99")(零误差,最直接) - ✅ 备选:已有 double d → 用
BigDecimal.valueOf(d)(内部转 String,安全简洁) - ⚠️ 谨慎:若必须用 double 构造,先转
Double.toString(d),再 new;String.valueOf(d)可能输出科学计数法(如 "1.23E5"),不可靠 - ❌ 禁止:
new BigDecimal(19.99)—— 此时 19.99 已是二进制近似值,错误被锁定
数据库交互:切断 double 中间链路
后端与数据库之间若经由 double 中转,BigDecimal 就形同虚设。必须确保全程定点数直通。
- 数据库字段类型必须为 DECIMAL(18,2) 或类似定点类型,禁用 FLOAT/DOUBLE
- 读取时调用
resultSet.getBigDecimal("amount"),别用getDouble()再转 - 写入时用
preparedStatement.setBigDecimal(1, amount),避免 JDBC 驱动自动降级为 double
运算过程:乘除必须主动控标度与舍入
加减法标度自动对齐,相对安全;乘除则极易失控,必须显式干预。
- 加减:
a.add(b)、a.subtract(b)一般无需额外处理 - 乘法:
a.multiply(b, new MathContext(10, RoundingMode.HALF_UP)),推荐带 MathContext 防位数爆炸 - 除法:
a.divide(b, 2, RoundingMode.HALF_EVEN)—— 必须指定小数位和舍入模式,否则可能抛 ArithmeticException - 最终输出统一收口:
result.setScale(2, RoundingMode.HALF_EVEN),确保金额始终保留两位小数
序列化输出:防止前端解析失真
后端返回的 "10.00" 若被 JSON 库默认 toString(),可能变成 10 或 1E1,前端 parseFloat 后就变 double,前功尽弃。
- FastJSON:启用
SerializerFeature.WriteBigDecimalAsPlain - Jackson:配置
WRITE_BIGDECIMAL_AS_PLAIN为 true - 核心效果:强制输出干净十进制字符串,如 "19.99",小数点后零不丢,无科学计数法
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











