java浮点数精度问题需绕过double缺陷:金融用bigdecimal(字符串构造)、性能敏感用整数放大法、科学计算用误差阈值比较。

Java 中处理浮点数运算精度问题,核心不是“修复 double”,而是“绕过它的缺陷”。0.1 + 0.2 得到 0.30000000000000004 这类现象,是 IEEE 754 二进制表示的必然结果,不是 bug,也无法靠四舍五入显示掩盖。真正有效的做法,是按场景选对工具、用对方式。
金融/金额类计算必须用 BigDecimal(字符串构造)
所有涉及钱的场景——订单、支付、计费、对账——都要求数值绝对可预测、可复现、可审计。
- ✅ 正确构造:永远用 new BigDecimal("19.99") 或 BigDecimal.valueOf(19.99);前者零误差,后者内部调用
Double.toString(),比直接传 double 安全 - ❌ 绝对避免:
new BigDecimal(19.99)—— 此时 19.99 在 double 里本就不精确,误差已写死 - 加减乘可直接用
.add()、.subtract()、.multiply();除法必须指定精度和舍入模式,例如:a.divide(b, 2, RoundingMode.HALF_EVEN)(银行家舍入,推荐用于财务) - 比较相等性必须用
bd1.compareTo(bd2) == 0,不要用equals()(它连 scale 都比较,“1.0” ≠ “1.00”)
性能敏感且小数位固定的场景可用整数放大法
比如温度保留 1 位、传感器读数保留 3 位、金额单位统一为“分”,全程走 long 运算,轻量又可靠。
- 金额存“分”:
long totalCents = 1999L + 2000L;→ 3999 分 = 39.99 元 - 放大时用
Math.round(original * scale),别用(long)(original * scale)(强转会截断) - 注意溢出:大数 × 大倍数可能超出 long 范围,必要时改用
BigInteger
科学/渲染/模拟类场景用误差阈值代替直接比较
这类业务允许微小偏差,重点在逻辑稳定而非绝对精确。
- 永远不要写
if (a == b)判断两个 double 是否相等 - 改用相对或绝对误差判断:
Math.abs(a - b) (普通计算),或更鲁棒的 <code>Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) - 避免混用 float/double,尤其在比较或混合运算中,极易因隐式转换引入意外偏差
前后端协同防精度污染
后端再严谨,前端传错原始值也白搭。
- 前端提交金额必须校验格式:
/^\d+(\.\d{1,2})?$/,拒绝 "19."、"19.990"、"1e2" - 禁止前端做任何金额计算(如实时满减),全部交由后端幂等接口完成
- 后端接收 JSON 时,配置
spring.jackson.deserialization.use-big-decimal-for-floats=true,确保金额字段反序列化为 BigDecimal 而非 double - 数据库字段定义为
DECIMAL(18,2),JDBC 读取用ResultSet.getBigDecimal(),跳过 double 中间层
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











