借用《Effactive Java》這本書中的話,float和double類型的主要設計目標是爲了科學計算和工程計算。他們執行二進制浮點運算,這是爲了在廣域數值範圍上提供較爲精確的快速近似計算而精心設計的。然而,它們沒有提供徹底精確的結果,因此不該該被用於要求精確結果的場合。可是,貨幣計算每每要求結果精確,這時候可使用int、long或BigDecimal。本文主要講述BigDecimal使用過程當中的一些陷阱、建議和技巧。 html
BigDecimal是不可變類,每個操做(加減乘除等)都會返回一個新的對象, 下面以加法操做爲例。 java
BigDecimal a =new BigDecimal("1.22"); System.out.println("construct with a String value: " + a); BigDecimal b =new BigDecimal("2.22"); a.add(b); System.out.println("a plus b is : " + a);
咱們很容易會認爲會輸出: 緩存
construct with a String value: 1.22 ide
a plus b is :3.44 函數
但實際上a plus b is : 1.22 測試
下面咱們就來分析一下加法操做的源碼 this
public BigDecimal add(BigDecimal augend) { long xs =this.intCompact; //整型數字表示的BigDecimal,例a的intCompact值爲122 long ys = augend.intCompact;//同上 BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intVal爲BigDecimal的一個BigInteger類型的屬性 BigInteger snd =(augend.intCompact !=INFLATED) ?null : augend.intVal; int rscale =this.scale;//小數位數 long sdiff = (long)rscale - augend.scale;//小數位數之差 if (sdiff != 0) {//取小數位數多的爲結果的小數位數 if (sdiff < 0) { int raise =checkScale(-sdiff); rscale =augend.scale; if (xs ==INFLATED ||(xs = longMultiplyPowerTen(xs,raise)) ==INFLATED) fst =bigMultiplyPowerTen(raise); }else { int raise =augend.checkScale(sdiff); if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED) snd = augend.bigMultiplyPowerTen(raise); } } if (xs !=INFLATED && ys !=INFLATED) { long sum = xs + ys; if ( (((sum ^ xs) &(sum ^ ys))) >= 0L)//判斷有無溢出 return BigDecimal.valueOf(sum,rscale);//返回使用BigDecimal的靜態工廠方法獲得的BigDecimal實例 } if (fst ==null) fst =BigInteger.valueOf(xs);//BigInteger的靜態工廠方法 if (snd ==null) snd =BigInteger.valueOf(ys); BigInteger sum =fst.add(snd); return (fst.signum == snd.signum) ?new BigDecimal(sum,INFLATED, rscale, 0) : new BigDecimal(sum,compactValFor(sum),rscale, 0);//返回經過其餘構造方法獲得的BigDecimal對象 }
由於BigInteger與BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,因此a.add(b)雖然作了加法操做,可是a並無保存加操做後的值,正確的用法應該是a=a.add(b); 減乘除操做也是同樣的返回一個新的BigDecimal對象。 spa
首先看以下一段代碼: 設計
// use constructor BigDecimal(double) BigDecimal aDouble =new BigDecimal(1.22); System.out.println("construct with a double value: " + aDouble); // use constructor BigDecimal(String) BigDecimal aString = new BigDecimal("1.22"); System.out.println("construct with a String value: " + aString); // use constructor BigDecimal.valueOf(double) BigDecimal aValue = BigDecimal.valueOf(1.22); System.out.println("use valueOf method: " + aValue);
你認爲輸出結果會是什麼呢?若是你認爲第一個會輸出1.22,那麼恭喜你答錯了,輸出結果以下: code
construct with a double value: 1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
use valueOf method: 1.22
爲何會這樣呢?JavaDoc對於BigDecimal(double)有很詳細的說明:
一、參數類型爲double的構造方法的結果有必定的不可預知性。有人可能認爲在Java中new BigDecimal(0.1)所建立的BigDecimal的值正好等於 0.1(非標度值 1,其標度爲 1),可是它實際上等於0.1000000000000000055511151231257827021181583404541015625。這是由於0.1沒法準確地表示爲 double(或者說對於該狀況,不能表示爲任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。
二、另外一方面,String 構造方法是徹底可預知的:new BigDecimal("0.1") 將建立一個 BigDecimal,它的值正好等於指望的0.1。所以,比較而言,一般建議優先使用String構造方法。
三、當 double 必須用做BigDecimal的來源時,請注意,此構造方法提供了一個精確轉換;它不提供與如下操做相同的結果:先使用Double.toString(double)方法將double轉換爲String,而後使用BigDecimal(String)構造方法。要獲取該結果,使用static valueOf(double)方法。
/** * Translates a {@code long} value into a {@code BigDecimal} * with a scale of zero. This {@literal "static factory method"} * is provided in preference to a ({@code long}) constructor * because it allows for reuse of frequently used * {@code BigDecimal} values. * * @param val value of the {@code BigDecimal}. * @return a {@code BigDecimal} whose value is {@code val}. */ public static BigDecimal valueOf(long val) { if (val >= 0 && val < zeroThroughTen.length) return zeroThroughTen[(int)val]; else if (val != INFLATED) return new BigDecimal(null, val, 0, 0); return new BigDecimal(INFLATED_BIGINT, val, 0, 0); } // Cache of common small BigDecimal values. private static final BigDecimal zeroThroughTen[] = { new BigDecimal(BigInteger.ZERO, 0, 0, 1), new BigDecimal(BigInteger.ONE, 1, 0, 1), new BigDecimal(BigInteger.valueOf(2), 2, 0, 1), new BigDecimal(BigInteger.valueOf(3), 3, 0, 1), new BigDecimal(BigInteger.valueOf(4), 4, 0, 1), new BigDecimal(BigInteger.valueOf(5), 5, 0, 1), new BigDecimal(BigInteger.valueOf(6), 6, 0, 1), new BigDecimal(BigInteger.valueOf(7), 7, 0, 1), new BigDecimal(BigInteger.valueOf(8), 8, 0, 1), new BigDecimal(BigInteger.valueOf(9), 9, 0, 1), new BigDecimal(BigInteger.TEN, 10, 0, 2), };
附上相應的測試代碼:
BigDecimal a1 = BigDecimal.valueOf(10); BigDecimal a2 = BigDecimal.valueOf(10); System.out.println(a1 == a2); // true BigDecimal a3 = BigDecimal.valueOf(11); BigDecimal a4 = BigDecimal.valueOf(11); System.out.println(a3 == a4); // false
BigDecimal.equals方法是有問題的.僅當你肯定比較的值有着相同的標度時纔可以使用. 所以,當你校驗相等性時注意 - BigDecimal有一個標度,用於相等性比較. 而compareTo方法則會忽略這個標度(scale).
BigDecimal的equals方法源碼以下:
@Override public boolean equals(Object x) { // 必須是BigDecimal實例 if (!(x instanceof BigDecimal)) return false; BigDecimal xDec = (BigDecimal) x; if (x == this) return true; // 標度必須相同 if (scale != xDec.scale) return false; long s = this.intCompact; long xs = xDec.intCompact; if (s != INFLATED) { if (xs == INFLATED) xs = compactValFor(xDec.intVal); return xs == s; } else if (xs != INFLATED) return xs == compactValFor(this.intVal); return this.inflated().equals(xDec.inflated()); }
參見如下測試代碼:
// 打印false System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00"))); // 打印false System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode()); // 打印0 System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")));
參見如下測試代碼:
//java.lang.ArithmeticException: Non-terminating decimal expansion; //no exact representable decimal result. try { BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3)); } catch (ArithmeticException ex) { System.out.println(ex.getMessage()); } // always use a scale and the rounding mode of your choice // 0.33 System.out.println(BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3), 2, BigDecimal.ROUND_HALF_UP));
(1)商業計算使用BigDecimal。
(2)使用參數類型爲String的構造函數,將double轉換成BigDecimal時用BigDecimal.valueOf(double),作除法運算時使用重載的方法divide(BigDecimal d, int scale, int roundMode)。
(3)BigDecimal是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,因此在作加減乘除運算時千萬要保存操做後的值。
(4)儘可能使用compareTo方法比較兩個BigDecimal對象的大小。
《Effective Java》
http://www.stichlberger.com/software/java-bigdecimal-gotchas/
http://stackoverflow.com/questions/7186204/bigdecimal-to-use-new-or-valueof
http://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html