BigDecimal精度與相等比較的坑


先想一下,創建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 = new BigDecimal("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         return false;
 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就相當不靠譜了,以后還是少用。

 

另外需要注意的一點是,寫代碼調別人寫的方法時,最好是點進去看一下實現。再小再常用的方法,都可能埋着大坑


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM