BigDecimal精度與相等比較的坑
阿新 • • 發佈:2018-09-13
大小 sys 不能 代碼 stat return 別人 小數類型 輸出
先想一下,創建BigDecimal對象的時候一般是怎麽創建的?
- new一個,傳進去值
- BigDecimal.valueOf方法,傳進去值
作為一個數字類型,經常有的操作是比較大小,有一種情況是比較是否相等。用equal方法還是compareTo方法?這裏就是一個大坑
1 //new 傳進去一個double 2 BigDecimal newZero = new BigDecimal(0.0); 3 System.out.println(BigDecimal.ZERO.equals(newZero)); 4 5 //new 傳進去一個字符串 6 BigDecimal stringNewZero = newBigDecimal("0.0"); 7 System.out.println(BigDecimal.ZERO.equals(stringNewZero)); 8 9 //valueOf 傳進去一個double 10 BigDecimal noScaleZero = BigDecimal.valueOf(0.0); 11 System.out.println(BigDecimal.ZERO.equals(noScaleZero)); 12 13 //valueOf 傳進去一個double,再手動設置精度為1 14 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1);15 System.out.println(BigDecimal.ZERO.equals(scaleZero));
用於比較的值全都是0,猜一猜上面幾個equals方法返回的結果是什麽?全都是true?no no no...
true false false false
驚不驚喜,意不意外?原因是什麽呢?看一下BigDecimal的equals方法的實現:
1 public boolean equals(Object x) { 2 //類型不同,直接返回false 3 if (!(x instanceof BigDecimal)) 4 returnfalse; 5 BigDecimal xDec = (BigDecimal) x; 6 //同一個對象,直接返回true 7 if (x == this) 8 return true; 9 //精度不同,直接返回false!! 10 if (scale != xDec.scale) 11 return false; 12 long s = this.intCompact; 13 long xs = xDec.intCompact; 14 if (s != INFLATED) { 15 if (xs == INFLATED) 16 xs = compactValFor(xDec.intVal); 17 return xs == s; 18 } else if (xs != INFLATED) 19 return xs == compactValFor(this.intVal); 20 21 return this.inflated().equals(xDec.inflated()); 22 }
從前面三個簡單的判斷就可以看出來,debug跟一下就知道是上面equals方法有三個返回false,都是因為精度不同。那麽BigDecimal.ZERO的精度是多少呢?看下源碼:
1 // Cache of common small BigDecimal values. 2 private static final BigDecimal zeroThroughTen[] = { 3 new BigDecimal(BigInteger.ZERO, 0, 0, 1), 4 new BigDecimal(BigInteger.ONE, 1, 0, 1), 5 new BigDecimal(BigInteger.valueOf(2), 2, 0, 1), 6 new BigDecimal(BigInteger.valueOf(3), 3, 0, 1), 7 new BigDecimal(BigInteger.valueOf(4), 4, 0, 1), 8 new BigDecimal(BigInteger.valueOf(5), 5, 0, 1), 9 new BigDecimal(BigInteger.valueOf(6), 6, 0, 1), 10 new BigDecimal(BigInteger.valueOf(7), 7, 0, 1), 11 new BigDecimal(BigInteger.valueOf(8), 8, 0, 1), 12 new BigDecimal(BigInteger.valueOf(9), 9, 0, 1), 13 new BigDecimal(BigInteger.TEN, 10, 0, 2), 14 }; 15 16 17 /** 18 * The value 0, with a scale of 0. 19 * 20 * @since 1.5 21 */ 22 public static final BigDecimal ZERO = zeroThroughTen[0];
BigDecimal.ZERO值為0,精度為0.
而上面幾種返回false的case,都是因為精度不同。精度不同的原因,則是BigDecimal對象初始化的方式不同,從源碼上看,前三種初始化的方式都不同。
所以說,BigDecimal比較大小,還是用compareTo方法比較靠譜,改為compareTo之後,上面四個case返回的結果都是相等:
1 BigDecimal newZero = new BigDecimal(0.0); 2 System.out.println(BigDecimal.ZERO.compareTo(newZero)); 3 4 BigDecimal stringNewZero = new BigDecimal("0.0"); 5 System.out.println(BigDecimal.ZERO.compareTo(stringNewZero)); 6 7 BigDecimal noScaleZero = BigDecimal.valueOf(0.0); 8 System.out.println(BigDecimal.ZERO.compareTo(noScaleZero)); 9 10 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1); 11 System.out.println(BigDecimal.ZERO.compareTo(scaleZero));
輸出結果
0 0 0 0
由此聯想到的一個更大的坑是,如果將BigDecimal的值作為HashMap的key,因為精度的問題,相同的值就可能出現hashCode值不同並且equals方法返回false,導致put和get就很可能會出現相同的值但是存取了不同的value。
再想一想,小數類型在計算機中本來就不能精確存儲,再把其作為HashMap的key就相當不靠譜了,以後還是少用。
另外需要註意的一點是,寫代碼調別人寫的方法時,最好是點進去看一下實現。再小再常用的方法,都可能埋著大坑
BigDecimal精度與相等比較的坑