Java中BigDecimal解析

 

java.math
類 BigDecimal

java.lang.Object  
java.lang.Number      
 java.math.BigDecimal
所有已實現的接口:
Serializable, Comparable<BigDecimal>

public class BigDecimal 
extends Number 
implements Comparable<BigDecimal> 
 
BigDecimal 是Java提供的一個不可變的、任意精度的有符號十進制數。BigDecimal 由任意精度的整數非標度值 和 32 位的整數標度 (scale) 組成。如果爲零或正數,則標度是小數點後的位數。如果爲負數,則將該數的非標度值乘以 10 的負 scale 次冪。因此,BigDecimal 表示的數值是 (unscaledValue × 10-scale)

    BigDecimal 類提供以下操作:算術、標度操作、舍入、比較、哈希算法和格式轉換。toString() 方法提供BigDecimal 的規範表示形式。

    BigDecimal 類使用戶能完全控制舍入行爲。如果未指定舍入模式,並且無法表示準確結果,則拋出一個異常;否則,通過向該操作提供適當的 MathContext 對象,可以對已選擇的精度和舍入模式執行計算。在任何情況下,可以爲舍入控制提供八種舍入模式。使用此類(例如,ROUND_HALF_UP)中的整數字段來表示舍入模式已過時;應改爲使用 RoundingMode enum(例如,RoundingMode.HALF_UP)的枚舉值。

    當爲 MathContext 對象提供 0 的精度設置(例如,MathContext.UNLIMITED)時,算術運算是準確的,它們是不採用任何 MathContext 對象的算術方法。(這是第 5 版之前的版本支持的惟一行爲。)爲了計算準確結果,不使用附帶 0 精度設置的 MathContext 對象的舍入模式設置,因此與該對象無關。在除法中,準確的商可能是一個無限長的十進制擴展;例如,1 除以 3 所得的商。如果商具有無窮的十進制擴展,但是指定了該操作返回準確結果,則拋出ArithmeticException。否則,像其他操作那樣,返回除法運算的準確結果。

    當精度設置不爲 0 時,BigDecimal 算法的規則完全符合 ANSI X3.274-1996 和 ANSI X3.274-1996/AM 1-2000( 7.4 節)中定義的算法的可選操作模式。與上述標準不同,BigDecimal 包括多種舍入模式,它們對於版本 5 以前的BigDecimal 版本中的除法是強制性的。這些 ANSI 標準和 BigDecimal 規範之間的任何衝突都按照有利於BigDecimal 的方式進行解決。

    由於同一數值可以有不同的表示形式(具有不同的標度),因此運算和舍入的規則必須同時指定數值結果和結果表示形式中所用的標度。

    一般情況下,當準確結果(在除法中,可能有無限多位)比返回的數值具有更多位數時,舍入模式和精度設置確定操作如何返回具有有限位數的結果。 首先,MathContext 的 precision 設置指定要返回的總位數;這確定了結果的精度。位數計數從準確結果的最左邊的非零數字開始。舍入模式確定丟棄的尾部位數如何影響返回的結果。

    對於所有算術運算符,運算的執行方式是,首先計算準確的中間結果,然後,使用選擇的舍入模式將其舍入爲精度設置(如有必要)指定的位數。如果不返回準確結果,則將丟棄準確結果的某些數位。當舍入增加了返回結果的大小時,前導數字“9”的進位傳播可能會創建新的數位。例如,將值 999.9 舍入爲三位數字,則在數值上等於一千,表示爲 100×101。在這種情況下,新的 "1" 是返回結果的前導數位。

除了邏輯的準確結果外,每種算術運算都有一個表示結果的首選標度。下表列出了每個運算的首選標度。
運算 結果的首選標度
Add max(addend.scale(), augend.scale())
Subtract max(minuend.scale(), subtrahend.scale())
Multiply multiplier.scale() + multiplicand.scale()
Divide dividend.scale() - divisor.scale()
這些標度是返回準確算術結果的方法使用的標度;準確相除可能必須使用較大的標度除外,因爲準確的結果可能有較多的位數。例如,1/32 得到 0.03125

舍入之前,邏輯的準確中間結果的標度是該運算的首選標度。如果用 precision 位數無法表示準確的數值結果,則舍入會選擇要返回的一組數字,並將該結果的標度從中間結果的標度減小到可以表示實際返回的 precision 位數的最小標度。如果準確結果可以使用最多 precision 個數字表示,則返回具有最接近首選標度的標度的結果表示形式。尤其是,通過移除結尾零並減少標度,可以用少於 precision 個數字來表示準確的可表示的商。例如,使用 floor 舍入模式將結果舍入爲三個數字,
19/100 = 0.19 // integer=19, scale=2 
但是
21/110 = 0.190 // integer=190, scale=3
    注意,對於加、減和乘,標度的縮減量將等於丟棄的準確結果的數字位置數。如果舍入導致進位傳播創建一個新的高位,則當未創建新的數位時,會丟棄該結果的附加數字。

    其他方法可能與舍入語義稍微不同。例如,使用指定的算法的 pow 方法得到的結果可能偶爾不同於舍入得到的算術結果,如最後一位有多個單位(ulp)。

    可以通過兩種類型的操作來處理 BigDecimal 的標度:標度/舍入操作和小數點移動操作。標度/舍入操作(setScale和 round)返回 BigDecimal,其值近似地(或精確地)等於操作數的值,但是其標度或精度是指定的值;即:它們會增加或減少對其值具有最小影響的存儲數的精度。小數點移動操作(movePointLeft 和 movePointRight)返回從操作數創建的 BigDecimal,創建的方法是按指定方向將小數點移動一個指定距離。

    爲了簡潔明瞭起見,整個 BigDecimal 方法的描述中都使用了僞代碼。僞代碼表達式 (i + j) 是“其值爲BigDecimal i 加 BigDecimal j 的 BigDecimal”的簡寫。僞代碼表達式 (i == j) 是“當且僅當 BigDecimal i 表示與 BigDecimal j 相同的值時,則爲 true”的簡寫。可以類似地解釋其他僞代碼表達式。方括號用於表示特定的BigInteger 和定義 BigDecimal 值的標度對;例如,[19, 2] 表示 BigDecimal 在數值上等於 0.19,標度是 2。

    注:如果 BigDecimal 對象用作 SortedMap 中的鍵或 SortedSet 中的元素,則應特別小心,因爲 BigDecimal 的自然排序與 equals 方法不一致。有關更多信息,請參見 ComparableSortedMap 或 SortedSet

    當爲任何輸入參數傳遞 null 對象引用時,此類的所有方法和構造方法都將拋出 NullPointerException

另請參見:
BigIntegerMathContextRoundingModeSortedMapSortedSet, 序列化表格


BigDecimal擴展:
    BigDecimal是Java提供的一個不變的、任意精度的有符號十進制數對象。它提供了多個個構造器,在這裏我們不關心,我們重點看用double和String構造的兩個構造器。
    BigDecimal(double)是把一個double類型十進制數構造爲一個BigDecimal對象實例。
    BigDecimal(String)是把一個以String表示的BigDecimal對象構造爲BigDecimal對象實例。
    習慣上,對於浮點數我們都會定義爲double或float,但BigDecimal API文檔中對於BigDecimal(double)有這麼一段話:
    Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .10000000000000000555111512312578 27021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances notwithstanding.
    The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") isexactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one

    下面對這段話做簡單解釋:
    注意:這個構造器的結果可能會有不可預知的結果。有人可能設想new BigDecimal(.1)等於.1是正確的,但它實際上是等於.1000000000000000055511151231257827021181583404541015625,這就是爲什麼.1不能用一個double精確表示的原因,因此,這個被放進構造器中的長值並不精確的等於.1,儘管外觀看起來是相等的。
    然而(String)構造器,則完全可預知的,new BigDecimal(“.1”)如同期望的那樣精確的等於.1,因此,(String)構造器是被優先推薦使用的。
    看下面的結果:
            System.out.println(new BigDecimal(123456789.02).toString());
            System.out.println(new BigDecimal("123456789.02").toString());
    輸出爲:
            123456789.01999999582767486572265625
            123456789.02
    現在我們知道,如果需要精確計算,非要用String來夠造BigDecimal不可!


實現方案
    現在我們已經知道怎麼解決這個問題了,原則上是使用BigDecimal(String)構造器,我們建議,在商業應用開發中,涉及金額等浮點數計算的數據,全部定義爲String,數據庫中可定義爲字符型字段,在需要使用這些數據進行運算的時候,使用BigDecimal(String)構造BigDecimal對象進行運算,保證數據的精確計算。同時避免了科學記數法的出現。如果科學記數表示法在應用中不是一種負擔的話,可以考慮定義爲浮點類型。這裏我們提供了一個工具類,定義浮點數的加、減、乘、除和四捨五入等運算方法。以供參考。

源文件MathExtend.java:

import java.math.BigDecimal;

public class MathExtend {
// 默認除法運算精度
private static final int DEFAULT_DIV_SCALE = 10;

public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}

public static String add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).toString();
}

public static double subtract(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}

public static String subtract(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).toString();
}

public static double multiply(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}

public static String multiply(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).toString();
}

public static double divide(double v1, double v2) {
return divide(v1, v2, DEFAULT_DIV_SCALE);
}

public static double divide(double v1, double v2, int scale) {
return divide(v1, v2, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static double divide(double v1, double v2, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, round_mode).doubleValue();
}

public static String divide(String v1, String v2) {
return divide(v1, v2, DEFAULT_DIV_SCALE);
}

public static String divide(String v1, String v2, int scale) {
return divide(v1, v2, DEFAULT_DIV_SCALE, BigDecimal.ROUND_HALF_EVEN);
}

public static String divide(String v1, String v2, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.divide(b2, scale, round_mode).toString();
}

public static double round(double v, int scale) {
return round(v, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static double round(double v, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, round_mode).doubleValue();
}

public static String round(String v, int scale) {
return round(v, scale, BigDecimal.ROUND_HALF_EVEN);
}

public static String round(String v, int scale, int round_mode) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, round_mode).toString();
}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章