我去,脸皮厚啊,你竟然使用==比较浮点数?
先看再點贊,給自己一點思考的時間,微信搜索【沉默王二】關注這個靠才華茍且的程序員。
本文?GitHub?github.com/itwanger?已收錄,里面還有一線大廠整理的面試題,以及我的系列文章。
老讀者都知道了,我在九朝古都洛陽的一家小作坊式的公司工作,身兼數職,談業務、敲代碼的同時帶兩個新人,其中一個就是大家熟知的小王,經常犯錯,被我寫到文章里。
不過,小王的心態一直很不錯,他不覺得被我批評有什么丟人的,反而每次讀完我的文章后覺得自己又升級了。因此,我覺得小王大有前途,再這么干個一兩年,老板要是覺得我的性價比低了,沒準就把我辭退留下小王了。一想到這,我竟然枯燥一笑了。
那天,我閑來無聊,就準備偷偷 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 0d1 和 d2 都為 1.1,所以 compareTo() 的結果就為 0,表示兩個值是相等的。
總結一下,在遇到浮點數的時候,千萬不要使用“==”操作符來進行比較,因為有精度問題。要么使用閾值來忽略舍入的問題,要么使用 BigDecimal 來替代 double 或者 float。
等會我就把這篇文章發給小王看看,同學們順手點個贊👍,讓小王不再感到那么孤單寂寞和冷。
我是沉默王二,一枚有顏值卻靠才華茍且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給。
注:如果文章有任何問題,歡迎毫不留情地指正。
很多讀者都同情我說,“二哥,你像母豬似的日更原創累不累?”我只能說寫作不易,且行且珍惜啊,關鍵是我真的喜歡寫作。最后,歡迎微信搜索「沉默王二」第一時間閱讀,回復「簡歷」更有阿里大佬的簡歷模板,本文?GitHub?github.com/itwanger?已收錄,歡迎 star。
總結
以上是生活随笔為你收集整理的我去,脸皮厚啊,你竟然使用==比较浮点数?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通频带、阻值、放大倍数之间的关系(Mul
- 下一篇: 网上书店