根本原因是十进制小数(如0.1、0.2)在二进制中为无限循环小数,而float(23位尾数,精度约6–7位)和double(52位尾数,精度约15–16位)受限于ieee 754固定位数存储,只能截断近似表示,导致固有舍入误差。

Java 中 float 和 double 的精度丢失,根本原因不是 Java 本身的问题,而是计算机用二进制表示十进制小数时的天然局限——很多常见小数(比如 0.1、0.2、0.66)在二进制中是无限循环小数,无法被有限位数精确存储。
浮点数在内存里怎么存的
float 占 32 位:1 位符号 + 8 位指数 + 23 位尾数(实际有效精度约 6–7 位十进制数字)
double 占 64 位:1 位符号 + 11 位指数 + 52 位尾数(实际有效精度约 15–16 位十进制数字)
关键点在于:尾数部分长度固定。像 0.1 这样的数,二进制展开是 0.00011001100110011…(无限循环),系统只能截断保存前若干位,后面就“丢”了——这不是计算错误,是存储能力限制。
这和十进制里写不出精确的 1/3(只能写 0.333…)是一个道理,只是换成了二进制视角。
哪些场景特别容易暴露精度问题
- 涉及金额、税率、库存数量等需要严格一致的业务逻辑
- 连续做多次加减乘除后比较结果(如
if (a + b == c)) - 把 float/double 当作唯一标识或用于集合去重(
HashSet<double></double>可能失效) - 大数值下对微小增量不敏感(例如
float f = 2324234234234234f; System.out.println(f == f + 1); // 输出 true)
真正靠谱的解决办法
不用 float/double 做精确计算,改用 java.math.BigDecimal:
- 构造时必须用 字符串,不能用 double 字面量:
new BigDecimal("0.1")✅new BigDecimal(0.1)❌(后者传入的 0.1 已经失真) - 所有运算用方法调用:
add()、subtract()、multiply()、divide()(除法需指定舍入模式,如RoundingMode.HALF_UP) - 比较大小别用
==或equals(),用compareTo()(它按数值比较,equals()还会比精度) - 数据库字段对应 decimal/numeric 类型,避免 JDBC 自动转成 double
什么时候可以继续用 float/double
它们没被废弃,是因为在合适场景下完全够用:
- 科学计算、图形渲染、物理模拟等允许误差的领域
- 性能敏感且精度要求不高的中间计算(比如归一化、权重估算)
- 仅作近似显示(如“耗时约 0.3 秒”),不参与后续逻辑判断
记住一个硬标准:只要业务上“1分钱都不能错”,就别碰 float 和 double。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











