在java語言中,double和float用於二進制浮點型計算,沒法獲得精確的結果。而BigDecimal則用於精確的計算。不超過16位有效數字(最好是不超過13位)的科學和工程計算,可使用double和float,但要求精確計或者超過了16位有效數字(超過13位也建議如此)的商業運算則須要使用BigDecimal進行運行,好比金融行業。html
《Effactive Java》第2版第48條中提到,「float和double類型尤爲不適合用於貨幣計算,由於要讓一個float或者double精確地標識0.1(或者10的任何其餘負數次方值)是不可能的。」 若是不進行運算,使用String表示金額都比double和float強,固然最好的是BigDecimal。金融行業http通訊接口可使用String來表示金額,若是內部接口調用,建議使用BigDecimal。java
BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。若是爲零或正數,則標度是小數點後的位數。若是爲負數,則將該數的非標度值乘以 10 的負scale 次冪。所以,BigDecimal表示的數值是(unscaledValue × 10-scale)。ide
分四類,一類是經過另外一個BigDecimal對象構造,一類是使用double/long/int/BigInteger類型的數值構造,一類是經過String字符串構造,最後一類是使用靜態方法valueOf()構造,靜態方法要求參數是double/long/int/BigInteger等。post
JDK API文檔中,對於public BigDecimal(double val) 解釋以下:測試
注:this
Double.toString(double)
方法,而後使用 BigDecimal(String)
構造方法,將 double 轉換爲 String。要獲取該結果,請使用 static valueOf(double)
方法。對於public static BigDecimal valueOf(long val)的解釋以下:url
故此,對於金融行業中的使用有如下結論:spa
1) 優先使用String參數的構造方法;.net
2)若是須要經過double構造BigDecimal,有限使用valueOf(double val)方法;這樣獲得的BigDecimal和String參數的構造方法標度一致(即標度可控);code
3)valueOf()的靜態工廠方法優先於long、int等類型參數的構造方法,
4)若是想獲取指定標度的初始化數值,好比2位小數的0,能夠以下使用:
BigDecimal zero1 = new BigDecimal("0.00");
String的構造方法,會根據字符串的小數位來設定標度,其餘的構造方法還須要額外調用setScale()來設定標度。
BigDecimal類的標度使用scale屬性表示,就是小數位數,精度使用precision屬性表示,就是有效位數(亦即整數位+小數位).
舍入方式有多種,BigDecimal類自己有舍入方式的靜態成員字段,可是已經廢棄,建議使用RoundingMode枚舉值。
RoundingMode枚舉值以下:
枚舉常量摘要 | |
---|---|
CEILING 向正無限大方向舍入的舍入模式。 |
|
DOWN 向零方向舍入的舍入模式。 |
|
FLOOR 向負無限大方向舍入的舍入模式。 |
|
HALF_DOWN 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向下舍入。 |
|
HALF_EVEN 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。 |
|
HALF_UP 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向上舍入。 |
|
UNNECESSARY 用於斷言請求的操做具備精確結果的舍入模式,所以不須要舍入。 |
|
UP 遠離零方向舍入的舍入模式。 |
其中HALF_UP就是最經常使用的四捨五入,其餘根據英文名字很好理解。
若是隻是對貨幣金額值的存取,使用String也能夠,但若是金額要參與四則運算,則必須使用BigDecimal進行精確的運算。BigDecimal與double、float的運算相比,BigDecimal運算比double、float運算精確,但速度沒有double、float的快。
BigDecimal的四則運算使用如下方法:public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //減法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法
JDK API中對四則運算的標度解釋以下:
對於全部算術運算符,運算的執行方式是,首先計算準確的中間結果,而後,使用選擇的舍入模式將其舍入爲精度設置(若有必要)指定的位數。若是不返回準確結果,則將丟棄準確結果的某些數位。當舍入增長了返回結果的大小時,前導數字「9」的進位傳播可能會建立新的數位。例如,將值 999.9 舍入爲三位數字,則在數值上等於一千,表示爲 100×101。在這種狀況下,新的 "1" 是返回結果的前導數位。
除了邏輯的準確結果外,每種算術運算都有一個表示結果的首選標度。下表列出了每一個運算的首選標度。
算術運算結果的首選標度運算 | 結果的首選標度 |
---|---|
加 | max(addend.scale(), augend.scale()) |
減 | max(minuend.scale(), subtrahend.scale()) |
乘 | multiplier.scale() + multiplicand.scale() |
除 | dividend.scale() - divisor.scale() |
這些標度是返回準確算術結果的方法使用的標度;準確相除可能必須使用較大的標度除外,由於準確的結果可能有較多的位數。例如,1/32 獲得 0.03125。
舍入以前,邏輯的準確中間結果的標度是該運算的首選標度。
由上面能夠看出來,若是調用上述四個方法的話,計算結果的標度可能不受控制,BigDecimal還爲每一個運算提供了幾個重載方法,能夠控制運算結果的標度和舍入方式。
以divide爲例進行解釋:
JDK API對public BigDecimal divide(BigDecimal divisor) 的解釋以下:
divisor
- 此
BigDecimal 要相除的值。
ArithmeticException
- 若是準確的商值沒有無窮的十進制擴展
能夠看出,沒法精確表示商值時就會拋異常,而除法很大可能沒法精確表示商值。因此,必須指定標度和舍入方式。如下幾個重載方法能夠供調用者指定標度和舍入方式。
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, MathContext mc)
這些方法也可能拋出異常,但異常的狀況是:divisor==0 或者 roundingMode==ROUND_UNNECESSARY 和 this.scale() 不足以準確地表示相除的結果。因此只要設置好標度和舍入方式,基本能夠保證不會拋異常。
6. BigDecimal的不可變性
BigDecimal有一個相似String的特性,就是不可變性。調用setScale或者add等運算方法後,原對象的scale和數值都不會改變,因此須要將方法的返回值進行保存使用。
好比:a = a.setScale(2); a = a.add(b). 其中a,b爲BigDecimal對象。
7.總結
1)金融行業的金額必定要使用BigDecimal來表示;
2)BigDecimal最好使用String的構造方法建立,若是要使用double等數值做爲參數,也要使用valueOf來建立對象。
3)BigDecimal進行四則運算時,最好指定其標度和舍入方式,不然可能拋異常。
4)BigDecimal是不可變的;四則運算後要使用對象保存結果;
5)多閱讀相關API文檔,寫測試代碼來驗證其相關方法的調動,才能更好的掌握。
參考資料:
Java BigDecimal詳解 (http://blog.csdn.net/jackiehff/article/details/8582449)
《Effactive Java》