java中float/double无法精确表示十进制小数,因二进制近似与ieee 754截断导致固有误差;需用bigdecimal(字符串构造)、整数单位或double+epsilon比较等方案应对不同场景。

Java 中的 float 和 double 无法做到真正“精确计算”十进制小数,因为它们本质是二进制近似表示——像 0.1、0.2 这类常见小数在二进制中是无限循环小数,而 IEEE 754 标准强制用固定位数(float 23 位尾数、double 52 位尾数)截断存储,误差从写入那一刻就已存在。所谓“精确计算”,其实是绕过浮点机制,改用能忠实表达十进制语义的方式。
用 BigDecimal 做真正可控的十进制计算
这是金融、计费、税务等零容错场景的唯一推荐方案:
- 构造必须用字符串:new BigDecimal("0.1") ✅;绝不用 new BigDecimal(0.1) ❌(后者传入的 double 已失真)
- 所有运算调用方法:add()、subtract()、multiply();除法 divide() 必须指定小数位数和舍入模式,例如 divide(other, 2, RoundingMode.HALF_UP)
- 比较用 compareTo():它按数值大小返回 -1/0/1;不要用 == 或 equals()(equals 会连 scale 一起比,new BigDecimal("1.0").equals(new BigDecimal("1")) 是 false)
- 数据库字段对应 decimal/numeric 类型,避免 JDBC 自动转成 double 导致二次失真
用整数单位彻底规避小数
适用于精度固定、单位明确的业务,比如金额、温度、时间:
- 把“元”换成“分”:19.99 元 → 存为 long 类型的 1999;所有加减乘除都在整数域完成
- 显示时再转换:String.format("%.2f", amount / 100.0),注意这只是格式化输出,不参与中间计算
- 优势是无精度损失、性能高、逻辑清晰;缺点是需人工管理缩放因子,不适合动态精度需求(如汇率实时变化到小数点后6位)
必须用 double 时的稳健比较与误差控制
科学计算、图形渲染、物理模拟等允许微小误差的场景,可继续用 double,但不能直接 ==:
- 定义合理 EPSILON:final double EPSILON = 1e-10;判断相等用 Math.abs(a - b)
- 注意量级匹配:对大数(如 1e12)应使用相对误差 Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b))
- 避免误差累积:减少连续浮点运算次数;关键中间结果及时 round 或转为 BigDecimal 截断
别踩这些典型陷阱
很多精度问题其实源于误用习惯:
- 用 double 做 HashMap 的 key 或 HashSet 的元素:因精度偏差导致 contains 失败
- 在循环中反复 += 0.1:for (int i = 0; i
- 认为 DecimalFormat.format() 能“修复”精度:它只影响显示,底层值仍是失真的 double
- 把 float/double 当作唯一标识或用于分布式系统中的状态比对
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











