浮点数的陷阱:你以为的 0.1 + 0.2其实不是 0.3,BigDecimal你真的用对了吗?
目录
一、引子:看似简单的加法为何出错?
(一)10% + 20% = 0.3?计算器为何不讲理?
0.1 + 0.2 ≠ 0.3 的反直觉现实
(二)从“理所当然”到“错愕”的转变
二、误差之源:浮点数精度陷阱
(一)为什么 0.1 不能被精确表示?
类比:1/3 在十进制中无法精确表示
(二)Java 中的 double 是如何存储的?(IEEE 754 简介)
举个例子:double d = 0.1;
(三)几个典型的反直觉计算案例
✅ 案例 1:加法误差
✅ 案例 2:金额加法
✅ 案例 3:金额减法
(四)小误差,大问题:一分钱的损失如何变成 30 万?
📌 背景
📌 问题
📌 教训
小结:你眼中的“0.1”,计算机眼中可能是“0.10000000000000001”
三、第一道防线:BigDecimal 的正确使用
(一)BigDecimal 真能解决一切吗?
(二)new BigDecimal(double) 的巨大坑
📌 问题演示
📌 背后机制
📌 推荐做法
✅ 正确性对比
(三)推荐构造方式及其区别
(四)scale 和 precision 的影响及副作用(通过实际乘法案例分析)
✅ 基本概念
📌 真实案例:乘法未指定舍入模式时抛出异常
📌 另一种误用:精度丢失
(五)小结:BigDecimal 很强,但你得用对方法
四、第二道防线:浮点数的格式化与舍入
(一)String.format 为何得出奇怪的 3.4 和 3.3?
📌 原因剖析
📌 银行家舍入模式解释(HALF_EVEN)
(二)RoundingMode 的各种模式与默认行为
(三)DecimalFormat vs BigDecimal:谁才靠谱?
✅ DecimalFormat:适合展示层(String)
✅ BigDecimal:适合运算和持久化
✅ 实战建议
推荐格式化方式:BigDecimal + setScale + RoundingMode
(四)小结:展示不等于计算,格式不等于精度
五、判等陷阱:float/double/BigDecimal 的“==”误会
(一)为什么 amount1 - amount2 == 1.05 为 false?
📌 原因解析
⚠️ 危险的误判后果
(二)equals() 和 compareTo() 的区别
📌 为什么 equals 不安全?
✅ 推荐:使用 compareTo() == 0 判等
(三)浮点判等的三种思路(场景实用指南)
✅ 思路一:绝对误差阈值(适用于 double)
✅ 思路二:BigDecimal 精确判等(适用于金额)
✅ 思路三:compareTo == 0 判等
(四)实战踩坑示例:开发者 A 的误判之痛
(五)小结:判等,是浮点世界最后一道坑
六、总结与建议:写给开发者的避坑手册
(一)🧠 浮点数开发四步法:从思考到落地
1. 存储方式选对了吗?
2. 运算方式可靠了吗?
3. 格式化环节安全了吗?
4. 比较操作正确了吗?
(二)✅ 最佳实践 Checklist:金融系统浮点数安全用法
(三)📌 什么时候可以“不那么较真”?
(四)💡 技术背后的意识:你是在建一个“可信赖的系统”
(五)🎯 最后的金句总结(送给每一位开发者)
📘 全文回顾
干货分享,感谢您的阅读!
“就加了个 0.1 + 0.2,怎么就不是 0.3 了?”
“同一个接口,线上调账 1 分,结果整整少了 300,000 元?”
听上去像是段子,但这类事故在金融系统、支付平台、清结算逻辑中并不罕见。
在程序世界里,0.1 + 0.2 != 0.3 并不是一个 bug,而是数学与计算机底层的差异冲突。浮点数的本质、Java 中 double 的局限、BigDecimal 的使用误区、格式化的陷阱、判等的误解……这一切都可能让你写下看似完美的代码,却埋下金额出错的隐雷。
这篇文章,将带你逐步揭开