浮点数精度丢失本质是二进制存储与十进制需求不匹配,非bug而是设计特性;应优先用bigdecimal,构造须用字符串或valueof,禁用double构造器,除法需指定精度与舍入模式。

Java 中浮点数精度丢失本质是二进制存储与十进制需求不匹配导致的,不是 bug,而是 float/double 的设计特性。解决核心就一条:该用 BigDecimal 的地方,绝不用 double 或 float。
为什么 float/double 一定会丢精度
0.1、0.2、0.3 这类常见小数,在二进制中是无限循环小数(类似十进制的 1/3 = 0.333…),而 float(23 位尾数)和 double(52 位尾数)只能截断存储。截断后存的就不是“0.1”,而是“0.10000000000000000555…”——后续所有运算都在这个有偏差的数上进行,误差自然累积。
典型表现:
- 0.1 + 0.2 == 0.3 → 结果为 false,实际输出 0.30000000000000004
- 1.0 - 0.9 → 输出 0.09999999999999998
- 循环累加 0.1 十次 → sum 不等于 1.0,而是 0.9999999999999999
BigDecimal 正确用法(避坑关键)
很多人用了 BigDecimal 还出错,问题几乎都出在构造和除法上。
- ✅ 构造必须用字符串或 valueOf:
new BigDecimal("0.1")或BigDecimal.valueOf(0.1)—— 这两种方式能真正表示“十进制的 0.1” - ❌ 绝对不用 double 构造器:
new BigDecimal(0.1)会把 double 原本的二进制误差原样带进来,结果仍是 0.10000000000000000555… - ✅ 四则运算用对应方法:
加:a.add(b);减:a.subtract(b);乘:a.multiply(b);
除:a.divide(b, scale, RoundingMode.HALF_UP)—— 除法必须指定小数位数和舍入模式,否则遇到 1÷3 这类无限小数直接抛 ArithmeticException
业务场景中的实用处理方式
不只是“换类型”,更要结合业务逻辑落地:
- 金额计算统一用“分”为单位:把 19.99 元存成整数 1999(单位:分),全程用 long 或 int 计算,彻底避开小数问题
-
显示前再转“元”:计算完用
new BigDecimal("1999").divide(BigDecimal.ONEHUNDRED, 2, RoundingMode.HALF_UP)得到 "19.99" -
比较两个值是否相等,不用 ==:
用bigA.compareTo(bigB) == 0,它比 equals() 更安全(equals 会比较 scale,"1.00" 和 "1.0" equals 返回 false) - 数据库字段类型同步:Java 用 BigDecimal,数据库对应字段必须是 DECIMAL 或 NUMERIC 类型,不能是 FLOAT/DOUBLE
什么情况下可以勉强用 double
仅限对精度无严格要求的场景:
- 科学模拟、图形渲染、机器学习中间计算(最终结果会四舍五入或可视化)
- 性能极度敏感且误差可接受(比如游戏物理引擎中位置偏移几像素无关紧要)
- 但只要涉及“钱、重量、税率、库存数量、合同条款数字”,一律禁用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











