1. 程式人生 > >Java如何正確比較浮點數

Java如何正確比較浮點數

看下面這段程式碼,將 d1 和 d2 兩個浮點數進行比較,輸出的結果會是什麼? ```java double d1 = .1 * 3; double d2 = .3; System.out.println(d1 == d2); ``` 按照正常邏輯來看,d1經過計算之後的結果應該是`0.3`,最後列印的結果應該是 `true`,對吧?但是執行一下就會發現結果並不是 `true` 而是 `false` 。 輸出一下 `d1`,發現得到的答案不是想象中的 `0.3` 而是 `0.30000000000000004`,所以和 `d2` 進行比較結果自然是 `false` 如何正確地比較浮點數(單精度的 float 和雙精度的 double),不單單是 Java 特定的問題,在計算機的記憶體中,儲存浮點數時使用的是 IEEE 754 標準,就會有精度的問題。 儲存和轉換的過程中浮點數容易引起一些較小的舍入誤差,正是這個原因,導致在比較浮點數的時候,不能使用“==”操作符——要求嚴格意義上的完全相等。 那麼如何正確的比較浮點數呢?這裡有兩種方案。 第一種方案是允許兩個值之間存在一點誤差(指定一個閾值),使用 `Math.abs()` 方法來計算兩個浮點數之間差異的絕對值,如果這個差異在閾值範圍之內,我們就認為兩個浮點數是相等的。 ```java final double THRESHOLD = .0001; double d1 = .1 * 3; double d2 = .3; 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.compareTo(b)` 如果 a 和 b 相等,則返回 0,否則返回 -1。 > tips: 不要使用 `equals()` 方法對兩個 BigDecimal 物件進行比較,這是因為 `equals()` 方法會考慮位數,如果位數不同,則會返回 false,儘管數學值是相等的。 ```java 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()` 方法比較的過程非常嚴謹,原始碼如下: ```java 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 來解決開頭的問題。 ```java BigDecimal d1 = new BigDecimal("0.1"); BigDecimal three = new BigDecimal("3"); BigDecimal d2 = new BigDecimal("0.3"); d1 = d1.multiply(three); System.out.println("d1 = " + d1); System.out.println("d2 = " + d2); System.out.println(d1.compareTo(d2)); ``` 程式輸出的結果如下: ```java d1 = 0.3 d2 = 0.3 0 ``` d1 和 d2 都為 0.3,所以 `compareTo()` 的結果就為 0,表示兩個值是相等的。 總結一下,在遇到浮點數的時候,千萬不要使用 `==` 操作符來進行比較,因為有精度問題。要麼使用閾值來忽略舍入的問題,要麼使用 BigDecimal 來替代 double 或者