我爱java系列---【Java比较浮点数的正确方式】
今天在读《Java开发手册》时,看到浮点数的等值判断问题「浮点数的基本数据类型不能用 ==
比较,包装数据类型不能用 equals
比较。以前开发时也遇到过浮点数的坑,所以就想一探究竟。
1. 浮点数表示
在计算机系统理论中,浮点数采用 IEEE 754 标准表示,编码方式是符号+阶码+尾数,如图:

比如 float 类型占用 32 位,单精度浮点表示法:
- 符号位(sign)占用 1 位,用来表示正负数,0 表示正数,1 表示负数
- 指数位(exponent)占用 8 位,用来表示指数,实际要加上偏移量
- 小数位(fraction)占用 23 位,用来表示小数,不足位数补 0
从这里可以看出,指数位决定了大小范围,小数位决定了计算精度。当十进制数值转换为二进制科学表达式后,得到的尾数位数是有可能很长甚至是无限长。所以当使用浮点格式来存储数字的时候,实际存储的尾数是被截取或执行舍入后的近似值。这就解释了浮点数计算不准确的问题,因为近似值和原值是有差异的。
2. 比较浮点数的方式
让我们来验证一下比较浮点数的几种方式。
2.1 ==
操作符
比较两个浮点数,一个从零开始加 11 次 0.1,另一个用 0.1 乘以 11 计算。然后用 ==
比较大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private void compareByOperator() { float f1 = 0.0f; for ( int i = 0; i < 11; i++) { f1 += 0.1f; } float f2 = 0.1f * 11; System. out .println( "f1 = " + f1); System. out .println( "f2 = " + f2); if (f1 == f2) { System. out .println( "f1 and f2 are equal using operator ==" ); } else { System. out .println( "f1 and f2 are not equal using operator ==" ); } } |
运行结果:
1 2 3 | f1 = 1.1000001 f2 = 1.1 f1 and f2 are not equal |
可以看到,两个浮点数不相等,所以通过 ==
来比较浮点数是不可靠的。
2.2 误差范围
指定一个误差范围,两个浮点数的差值在范围之内,则认为是相等的。使用 Math.abs()
计算差值,然后和阈值比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private void compareByThreshold() { final float THRESHOLD = 0.000001; float f1 = 0.0f; for ( int i = 0; i < 11; i++) { f1 += 0.1f; } float f2 = 0.1f * 11; System. out .println( "f1 = " + f1); System. out .println( "f2 = " + f2); if (Math.abs(f1 - f2) < THRESHOLD) { System. out .println( "f1 and f2 are equal using threshold" ); } else { System. out .println( "f1 and f2 are not equal using threshold" ); } } |
运行输出:
1 2 3 | f1 = 1.1000001 f2 = 1.1 f1 and f2 are equal using threshold |
2.3 使用 BigDecimal
BigDecimal
是不可变的,能够精确地表示十进制数字。需要注意的是,创建 BigDecimal
对象时,要使用参数为 String
的构造方法,不要使用构造参数为 double
的,如果非要使用 double
创建,一定要用 valueOf
静态方法,防止丢失精度。然后调用 compareTo
方法比较即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private void compareByBigDecimal() { BigDecimal f1 = new BigDecimal( "0.0" ); BigDecimal pointOne = new BigDecimal( "0.1" ); for ( int i = 0; i < 11; i++) { f1 = f1.add(pointOne); } BigDecimal f2 = new BigDecimal( "0.1" ); BigDecimal eleven = new BigDecimal( "11" ); f2 = f2.multiply(eleven); System. out .println( "f1 = " + f1); System. out .println( "f2 = " + f2); if (f1.compareTo(f2) == 0) { System. out .println( "f1 and f2 are equal using BigDecimal" ); } else { System. out .println( "f1 and f2 are not equal using BigDecimal" ); } } |
运行输出:
1 2 3 | f1 = 1.1 f2 = 1.1 f1 and f2 are equal using BigDecimal |
3. 结论
使用 ==
比较浮点数不准确,可以采用误差范围近似相等,或者 BigDecimal
计算比较。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2019-09-01 我爱java系列---【日期转换工具类】
2019-09-01 我爱java系列---【访问购物车未登录时自动跳转到登录页面,登录成功自动返回购物车页面】
2019-09-01 开发中遇到的问题---【浏览器的cookie中没有值】
2019-09-01 我爱java系列---【微服务中feign拦截器的使用】