Java BigDecimal初探
更新時間:2016-03-17
一、引言
《Effactive Java》中有這樣的描述:float
和double
類型的主要設計目標是為了科學計算和工程計算。他們執行二進制浮點運算,這是為了在廣域數值範圍上提供較為精確的快速近似計算而精心設計的。然而,它們沒有提供完全精確的結果,所以不應該被用於要求精確結果的場合。但是,貨幣計算往往要求結果精確,這時候可以使用int
、long
或BigDecimal
。
二、不可變性
BigDecimal
是不可變類,每一個操作(加減乘除等)都會返回一個新的對象, 下面以加法操作為例:
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
a plus b is :3.44
但實際上a plus b is : 1.22
因為BigDecimal
是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以a.add(b)
雖然做了加法操作,但是a並沒有保存加操作後的值,正確的用法應該是a=a.add(b);
減乘除操作也是一樣的返回一個新的BigDecimal
對象。
三、構造函數和valueOf方法
首先看如下一段代碼:
// 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,那麽恭喜你答錯了,輸出結果如下:
construct with a double value: 1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
use valueOf method: 1.22
為什麽會這樣呢?JavaDoc對於BigDecimal(double)
有很詳細的說明:
1、參數類型為double
的構造方法的結果有一定的不可預知性。有人可能認為在Java中new BigDecimal(0.1)
所創建的BigDecimal
double
(或者說對於該情況,不能表示為任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。
2、另一方面,String
構造方法是完全可預知的:new BigDecimal("0.1")
將創建一個 BigDecimal,它的值正好等於期望的0.1。因此,比較而言,通常建議優先使用String
構造方法。
3、當 double
必須用作BigDecimal
的來源時,請註意,此構造方法提供了一個精確轉換;它不提供與以下操作相同的結果:先使用Double.toString(double)
方法將double
轉換為String
,然後使用BigDecimal(String)
構造方法。要獲取該結果,使用static valueOf(double)
方法。
BigDecimal.valueOf(double)
使用由 Double.toString(double)
方法提供的 double
的標準化字符串表示形式( canonical string representation) 將 double
轉換成 BigDecimal
。這也是比較推薦的一種方式。
BigDecimal.valueOf(double)
還有一個重載的方法 BigDecimal.valueOf(long)
,對於某些常用值(0到10) BigDecimal
在內部做了緩存, 如果傳遞的參數值範圍為[0, 10], 這個方法直接返回緩存中相應的BigDecimal
對象。
四、equals方法
BigDecimal.equals
方法是有問題的。僅當你確定比較的值有著相同的標度時才可使用。因此,當你校驗相等性時註意BigDecimal
有一個標度,用於相等性比較。而compareTo
方法則會忽略這個標度(scale)。
參見以下測試代碼:
// 打印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")));
五、對除法使用標度
BigDecimal
對象的精度沒有限制。如果結果不能終止,divide方法將會拋出ArithmeticException
, 如1 / 3 = 0.33333...。所以強烈推薦使用重載方法divide(BigDecimal d, int scale, int roundMode)
指定標度和舍入模式來避免以上異常。
關於舍入模式常用的有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
對象的大小。
Java BigDecimal初探