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


1.引言

  在java語言中,double和float用於二進制浮點型計算,無法得到精確的結果。而BigDecimal則用於精確的計算。不超過16位有效數字(最好是不超過13位)的科學和工程計算,可以使用double和float,但要求精確計或者超過了16位有效數字(超過13位也建議如此)的商業運算則需要使用BigDecimal進行運行,比如金融行業。

  《Effactive Java》第2版第48條中提到,“float和double類型尤其不適合用於貨幣計算,因為要讓一個float或者double精確地標識0.1(或者10的任何其他負數次方值)是不可能的。” 如果不進行運算,使用String表示金額都比double和float強,當然最好的是BigDecimal。金融行業http通信接口可以使用String來表示金額,如果內部接口調用,建議使用BigDecimal。

2.BigDecimal簡介

  BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。如果為零或正數,則標度是小數點后的位數。如果為負數,則將該數的非標度值乘以 10 的負scale 次冪。因此,BigDecimal表示的數值是(unscaledValue × 10-scale)

3. BigDecimal的構造方法

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

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

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

注:

  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)的解釋如下:

long 值轉換為具有零標度的 BigDecimal。提供的 此“靜態工廠方法”優先於 (long) 構造方法,因為前者允許重用經常使用的 BigDecimal

故此,對於金融行業中的使用有以下結論:

1) 優先使用String參數的構造方法;

2)如果需要通過double構造BigDecimal,有限使用valueOf(double val)方法;這樣得到的BigDecimal和String參數的構造方法標度一致(即標度可控);

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)


免責聲明!

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



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