java中BigDecimal在金融行業中的使用

1.引言

  在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

2.BigDecimal簡介

  BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。若是爲零或正數,則標度是小數點後的位數。若是爲負數,則將該數的非標度值乘以 10 的負scale 次冪。所以,BigDecimal表示的數值是(unscaledValue × 10-scale)ide

3. BigDecimal的構造方法

  分四類,一類是經過另外一個BigDecimal對象構造,一類是使用double/long/int/BigInteger類型的數值構造,一類是經過String字符串構造,最後一類是使用靜態方法valueOf()構造,靜態方法要求參數是double/long/int/BigInteger等。post

  JDK API文檔中,對於public BigDecimal(double val) 解釋以下:測試

double 轉換爲 BigDecimal,後者是 double 的二進制浮點值準確的十進制表示形式。返回的 BigDecimal 的標度是使 (10scale × val) 爲整數的最小值。

注:this

  1. 此構造方法的結果有必定的不可預知性。有人可能認爲在 Java 中寫入 new BigDecimal(0.1) 所建立的 BigDecimal 正好等於 0.1(非標度值 1,其標度爲 1),可是它實際上等於 0.1000000000000000055511151231257827021181583404541015625。這是由於 0.1 沒法準確地表示爲 double(或者說對於該狀況,不能表示爲任何有限長度的二進制小數)。這樣,傳入 到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。
  2. 另外一方面,String 構造方法是徹底可預知的:寫入 new BigDecimal("0.1") 將建立一個 BigDecimal,它正好 等於預期的 0.1。所以,比較而言,一般建議優先使用 String 構造方法
  3. double 必須用做 BigDecimal 的源時,請注意,此構造方法提供了一個準確轉換;它不提供與如下操做相同的結果:先使用 Double.toString(double) 方法,而後使用 BigDecimal(String) 構造方法,將 double 轉換爲 String。要獲取該結果,請使用 static valueOf(double) 方法

對於public static BigDecimal valueOf(long val)的解釋以下:url

long 值轉換爲具備零標度的 BigDecimal。提供的 此「靜態工廠方法」優先於 (long) 構造方法,由於前者容許重用常用的 BigDecimal

故此,對於金融行業中的使用有如下結論: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()來設定標度。

4. BigDecimal的標度、舍入方式

BigDecimal類的標度使用scale屬性表示,就是小數位數,精度使用precision屬性表示,就是有效位數(亦即整數位+小數位).

舍入方式有多種,BigDecimal類自己有舍入方式的靜態成員字段,可是已經廢棄,建議使用RoundingMode枚舉值。

RoundingMode枚舉值以下:

枚舉常量摘要
CEILING
          向正無限大方向舍入的舍入模式。
DOWN
          向零方向舍入的舍入模式。
FLOOR
          向負無限大方向舍入的舍入模式。
HALF_DOWN
          向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向下舍入。
HALF_EVEN
          向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。
HALF_UP
          向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向上舍入。
UNNECESSARY
          用於斷言請求的操做具備精確結果的舍入模式,所以不須要舍入。
UP
          遠離零方向舍入的舍入模式。

 

 其中HALF_UP就是最經常使用的四捨五入,其餘根據英文名字很好理解。

5.BigDecimal的四則運算

若是隻是對貨幣金額值的存取,使用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) 的解釋以下:

返回一個 BigDecimal,其值爲 (this / divisor),其 首選標度爲 (this.scale() - divisor.scale())若是沒法表示準確的商值(由於它有無窮的十進制擴展),則拋出 ArithmeticException 
參數:
divisor - 此 BigDecimal 要相除的值。
返回:
this / divisor
拋出:
ArithmeticException - 若是準確的商值沒有無窮的十進制擴展
從如下版本開始:
1.5 
能夠看出,沒法精確表示商值時就會拋異常,而除法很大可能沒法精確表示商值。因此,必須指定標度和舍入方式。如下幾個重載方法能夠供調用者指定標度和舍入方式。
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_UNNECESSARYthis.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》

使用BigDecimal進行精確運算(http://www.cnblogs.com/chenssy/archive/2012/09/09/2677279.html)

相關文章
相關標籤/搜索