作者:沉默王二
链接:https://juejin.im/post/5f0fb2e6f265da22ab2d68fa
来源:掘金

老读者都知道了,我在九朝古都洛阳的一家小作坊式的公司工作,身兼数职,谈业务、敲代码的同时带两个新人,其中一个就是大家熟知的小王,经常犯错,被我写到文章里。

不过,小王的心态一直很不错,他不觉得被我批评有什么丢人的,反而每次读完我的文章后觉得自己又升级了。因此,我觉得小王大有前途,再这么干个一两年,老板要是觉得我的性价比低了,没准就把我辞退留下小王了。一想到这,我竟然枯燥一笑了。

那天,我闲来无聊,就准备偷偷 review 一下小王的代码,看能不能鸡蛋里挑点骨头,没想到,还真的被我挑到了。

double d1 = .0;
for (int i = 1; i <= 11; i++) {d1 += .1;
}double d2 = .1 * 11;System.out.println(d1 == d2);

小王这段代码蛮炫技的,其实,尤其是 .0、.1 的写法,我平常都老实巴交的写成 0.0、0.1,从来没想着要把小数点前面的 0 省略。

按照正常的逻辑来看,d1 在经过 11 次循环加 .1 后的结果应该是 1.1,d2 通过 .1 乘以 11 后的结果也应该是 1.1,最后打印出来的结果应该是 true,对吧?小王应该也是这么期待的,我觉得。

但我当时硬是没忍住我的暴脾气,破口大骂:“我擦,小王,你竟然敢用 == 比较浮点数,这不是找刺激吗?”

如果有读者也觉得输出结果是 true 的话,可以把上面这段代码在本地运行一下,输出的结果一定会出乎你的意料。

false

对,false,我没骗你。如何正确地比较浮点数(单精度的 float 和双精度的 double),不单单是 Java 特定的问题,很多编程语言的初学者也会遇到同样的问题。在计算机的内存中,存储浮点数时使用的是 IEEE 754 标准,就会有精度的问题,至于实际上的存储转换过程,这篇文章不做过多的探讨。

(主要是我太菜了,探讨的过程很枯燥,一点都不有趣,严谨地理论推导就交给那些真正的技术大佬们吧,我就不献丑了。)

同学们只需要知道,存储和转换的过程中浮点数容易引起一些较小的舍入误差,正是这个原因,导致在比较浮点数的时候,不能使用“==”操作符——要求严格意义上的完全相等。

再来看一下小王的代码,我们把 d1 和 d2 打印出来,看看它们的值到底是什么。

d1:1.0999999999999999
d2:1.1

怪不得“==”的时候输出 false,原来 d1 的值有一些误差,并不是我们预期的 1.1。既然“==”不能用来比较浮点数,那么小王就得挨骂,这逻辑讲得通吧?

那这个问题该怎么解决呢?

对于浮点数的存储和转化问题,我表示无能为力,这是实在话,计算机的底层问题,驾驭不了。但是,可以通过一些折中的办法,比如说允许两个值之间有点误差(指定一个阈值),小到 0.000000…..1,具体多少个 0 懒得数了,反正特别小,那么我们就认为两个浮点数是相等的。

第一种方案就是使用 Math.abs() 方法来计算两个浮点数之间的差异,如果这个差异在阈值范围之内,我们就认为两个浮点数是相等。

final double THRESHOLD = .0001;double d1 = .0;
for (int i = 1; i <= 11; i++) {d1 += .1;
}double d2 = .1 * 11;if(Math.abs(d1-d2) < THRESHOLD) {System.out.println("d1 和 d2 相等");
} else {System.out.println("d1 和 d2 不等");
}

Math.abs() 方法用来返回 double 的绝对值,如果 double 小于 0,则返回 double 的正值,否则返回 double。也就是说,abs() 后的结果绝对大于 0,如果结果小于阈值(THRESHOLD),我们就认为 d1 和 d2 相等。

第二种解决方案就是使用 BigDecimal 类,可以指定要舍入的模式和精度,这样就可以解决舍入的误差。

可以使用 BigDecimal 类的 compareTo() 方法对两个数进行比较,该方法将会忽略小数点后的位数,怎么理解这句话呢?比如说 2.0 和 2.00 的位数不同,但它俩的值是相等的。

如果 a 小于 b,则该方法返回 -1,如果相等,则返回 0,否则返回 -1。

注意,千万不要使用 equals() 方法对两个 BigDecimal 对象进行比较,这是因为 equals() 方法会考虑位数,如果位数不同,则会返回 false,尽管数学值是相等的。

BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");System.out.println(a.equals(b));
System.out.println(a.compareTo(b) == 0);

a.equals(b) 的结果就为 false,因为 2.00 和 2.0 小数点后的位数不同,但 a.compareTo(b) == 0 的结果就为 true,因为 2.00 和 2.0 在数学层面的值的确是相等的。

compareTo() 方法比较的过程非常严谨,感兴趣的同学可以查看一下源码,其中位数不同的时候,会执行以下方法进行比较。

private int compareMagnitude(BigDecimal val) {// Match scales, avoid unnecessary inflationlong ys = val.intCompact;long xs = this.intCompact;if (xs == 0)return (ys == 0) ? 0 : -1;if (ys == 0)return 1;long sdiff = (long)this.scale - val.scale;if (sdiff != 0) {// Avoid matching scales if the (adjusted) exponents differlong xae = (long)this.precision() - this.scale;   // [-1]long yae = (long)val.precision() - val.scale;     // [-1]if (xae < yae)return -1;if (xae > yae)return 1;if (sdiff < 0) {// The cases sdiff <= Integer.MIN_VALUE intentionally fall through.if ( sdiff > Integer.MIN_VALUE &&(xs == INFLATED ||(xs = longMultiplyPowerTen(xs, (int)-sdiff)) == INFLATED) &&ys == INFLATED) {BigInteger rb = bigMultiplyPowerTen((int)-sdiff);return rb.compareMagnitude(val.intVal);}} else { // sdiff > 0// The cases sdiff > Integer.MAX_VALUE intentionally fall through.if ( sdiff <= Integer.MAX_VALUE &&(ys == INFLATED ||(ys = longMultiplyPowerTen(ys, (int)sdiff)) == INFLATED) &&xs == INFLATED) {BigInteger rb = val.bigMultiplyPowerTen((int)sdiff);return this.intVal.compareMagnitude(rb);}}}if (xs != INFLATED)return (ys != INFLATED) ? longCompareMagnitude(xs, ys) : -1;else if (ys != INFLATED)return 1;elsereturn this.intVal.compareMagnitude(val.intVal);
}

好了,现在让我们使用 BigDecimal 来解决精度问题吧。

BigDecimal d1 = new BigDecimal("0.0");
BigDecimal pointOne = new BigDecimal("0.1");
for (int i = 1; i <= 11; i++) {d1 = d1.add(pointOne);
}BigDecimal d2 = new BigDecimal("0.1");
BigDecimal eleven = new BigDecimal("11");
d2 = d2.multiply(eleven);System.out.println("d1 = " + d1);
System.out.println("d2 = " + d2);System.out.println(d1.compareTo(d2));

程序输出的结果如下:

d1 = 1.1
d2 = 1.1
0

d1 和 d2 都为 1.1,所以 compareTo() 的结果就为 0,表示两个值是相等的。

总结一下,在遇到浮点数的时候,千万不要使用“==”操作符来进行比较,因为有精度问题。要么使用阈值来忽略舍入的问题,要么使用 BigDecimal 来替代 double 或者 float。

等会我就把这篇文章发给小王看看,同学们顺手点个赞,让小王不再感到那么孤单寂寞和冷。

两个double比较大小_我去,脸皮厚啊,你竟然使用==比较浮点数?相关推荐

  1. python中怎么比较两个列表的大小_在Python中比较两个大小不同的列表

    我有两张不同尺寸的单子.一个有产品名称,另一个有品牌名称(可以是一个词或多个词). 我需要检查产品名称是否有确切的品牌名称(存在于品牌列表中)并提取相同的其他返回空列表. 我在提取匹配的品牌名称时面临 ...

  2. Java中比较两个Double类型数据的大小

    在java中int类型比较可以用"==",而double类型的数据不能用"= ="比较,否则得到永不相等的结果. 一般可以Double的doubleToLong ...

  3. jsp中两个double相乘_图像处理中的代数运算及几何变换

    图像运算是图像处理中常用的处理方法,它以图像为单位进行操作,运算的结果是一副新的图像,常常用于图像的高级处理(如图像分割,目标的检测和识别等)的前期处理.具体的图像运算包括点运算,代数运算,几何运算和 ...

  4. 两直线平行交叉相乘_人教版初中数学七年级下册 平行线判定2公开课优质课课件教案视频...

    平 行 线 的 判 定 一.教材分析 1.主要内容及其地位 本节的主要内容是平行线的判定公理及两个判定定理,由分析画平行线的过程得知,画平行线实际上就是画相等的同位角,由此得到平行线的判定公理--&q ...

  5. 两平面平行方向向量关系_一文读懂 GDT 中的平面度

    本文从以下方面介绍平面度: 1. 数学定义(文末附带向量基础知识) 2. 接触模拟方法 3. 图纸标注 4. 应用场合 5. 取值方法 6 加工方法 7. 测量方法 一.数学定义 马克思有句名言:一门 ...

  6. C中不能直接比较两个double类型

    在比较float 和double类型的时候,因为float/double精度的问题,比如1.000000001可能和1.0000000000001相等,不应该直接使用a>b等类似的方式进行比较, ...

  7. double类型大小比较的方法

    问题 在Java中,int类型数据的大小比较可以使用双等号,double类型则不能使用双等号比较大小,那若使用double类型时怎么进行比较呢? 方法 转换为字符串 如果要比较的两个double数据的 ...

  8. 正方形类的定义,比较两个日期的大小并计算其间隔天数

    实验项目5:常用实用类 一.实验目的和要求 学会覆盖Object类中常用方法: 学会使用Math类中常用方法: 学会字符串的常用操作: 学会常用日期类的使用方法. 二.实验内容与步骤 1.定义一个名为 ...

  9. Java中double类型大小比较的五种方法

    文章目录 1.使用BigDecimal 2.使用包装类Double 3.在误差范围内运行相等 4.转换成字符串 5.使用doubleToLongBits()方法 在Java中 int类型数据的大小比较 ...

最新文章

  1. Java缓存学习之五:spring 对缓存的支持
  2. 在pymongo中使用distinct
  3. vc中出现stack overflow错误(VS设置默认栈大小)
  4. XT711(大陆行货)刷机与优化指南
  5. 什么原因?全球许多网络提供商推迟部署IPv6
  6. sys.stdout sys.stderr的用法
  7. php swoole环境搭建,windows系统php环境安装swoole具体步骤
  8. Quartz(任务调度)- job串行避免死锁
  9. opencv中的矩阵拼接
  10. Java 面向对象的设计思维
  11. (王道408考研数据结构)第六章图-第四节7:关键路径(最早发生时间、最迟发生时间)
  12. android webview 水平滚动,Android WebView不可滚动
  13. 推送后更改git commit消息(假设没有人从远程拉出)
  14. DevExpress XtraTreeList的复选框 禁用
  15. webstorm中文版修改
  16. android 手机内存64实际不到,我手机64G都天天清理,为什么内存越来越少?原来方法不对...
  17. Linux应该怎么快速学习?首推这份全网爆火的“Linux速成笔记”,阿里架构师都在用它!
  18. JS基礎:Hoisting 變量提升、TDZ 暫時性死區(Temporal Dead Zone)
  19. 【渝粤教育】国家开放大学2019年春季 2444酒店管理概论 参考试题
  20. 写一篇简单的TileMap入门教程

热门文章

  1. Java 的几把 JVM 级锁
  2. Hybrid的配置(一)
  3. Python 生成字典序(生成下一个字典序)
  4. stm32f的一些问题
  5. winform4、C#中WinForm程序退出方法(释放资源)
  6. 数据预处理和特征选择
  7. 服务器删除系统痕迹,win10怎么清除使用痕迹_网站服务器运行维护
  8. 这份最新阿里、腾讯、华为、字节等大厂的薪资和职级对比,你在哪个阶段?
  9. 使用Python+OpenCV实现在视频中某对象后添加图像
  10. ubuntu16.04 catkin_make报错No rule to make target '/usr/lib/x86_64-linux-gnu/libGL.so'