先看再点赞,给本身一点思考的时间,微信搜索【沉默王二】关注这个靠才华苟且的程序员。
本文 GitHub github.com/itwanger 已收录,里面还有一线大厂整理的面试题,以及个人系列文章。java
老读者都知道了,我在九朝古都洛阳的一家小做坊式的公司工做,身兼数职,谈业务、敲代码的同时带两个新人,其中一个就是你们熟知的小王,常常犯错,被我写到文章里。git
不过,小王的心态一直很不错,他不以为被我批评有什么丢人的,反而每次读完个人文章后以为本身又升级了。所以,我以为小王大有前途,再这么干个一两年,老板要是以为个人性价比低了,没准就把我辞退留下小王了。一想到这,我居然枯燥一笑了。程序员
那天,我闲来无聊,就准备偷偷 review 一下小王的代码,看能不能鸡蛋里挑点骨头,没想到,还真的被我挑到了。github
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 省略。web
按照正常的逻辑来看,d1 在通过 11 次循环加 .1
后的结果应该是 1.1
,d2 经过 .1
乘以 11 后的结果也应该是 1.1
,最后打印出来的结果应该是 true,对吧?小王应该也是这么期待的,我以为。面试
但我当时硬是没忍住个人暴脾气,破口大骂:“我擦,小王,你居然敢用 == 比较浮点数,这不是找刺激吗?”编程
若是有读者也以为输出结果是 true 的话,能够把上面这段代码在本地运行一下,输出的结果必定会出乎你的意料。微信
false
对,false,我没骗你。如何正确地比较浮点数(单精度的 float 和双精度的 double),不仅仅是 Java 特定的问题,不少编程语言的初学者也会遇到一样的问题。在计算机的内存中,存储浮点数时使用的是 IEEE 754 标准,就会有精度的问题,至于实际上的存储转换过程,这篇文章不作过多的探讨。编程语言
(主要是我太菜了,探讨的过程很枯燥,一点都不有趣,严谨地理论推导就交给那些真正的技术大佬们吧,我就不献丑了。)svg
同窗们只须要知道,存储和转换的过程当中浮点数容易引发一些较小的舍入偏差,正是这个缘由,致使在比较浮点数的时候,不能使用“==”操做符——要求严格意义上的彻底相等。
再来看一下小王的代码,咱们把 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 inflation long 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 differ long 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; else return 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。
等会我就把这篇文章发给小王看看,同窗们顺手点个赞👍,让小王再也不感到那么孤单寂寞和冷。
我是沉默王二,一枚有颜值却靠才华苟且的程序员。关注便可提高学习效率,别忘了三连啊,点赞、收藏、留言,我不挑,奥利给。
注:若是文章有任何问题,欢迎绝不留情地指正。
不少读者都同情我说,“二哥,你像母猪似的日更原创累不累?”我只能说写做不易,且行且珍惜啊,关键是我真的喜欢写做。最后,欢迎微信搜索「沉默王二」第一时间阅读,回复「简历」更有阿里大佬的简历模板,本文 GitHub github.com/itwanger 已收录,欢迎 star。