前面介紹的BigInteger只能表達任意整數,但不能表達小數,要想表達任意小數,還需專門的大小數類型BigDecimal。如果說設計BigInteger的目的是替代int和long類型,那麼設計BigDecimal的目的便是替代浮點型float和雙精度型double了。正如它的兄弟BigInteg ...
前面介紹的BigInteger只能表達任意整數,但不能表達小數,要想表達任意小數,還需專門的大小數類型BigDecimal。如果說設計BigInteger的目的是替代int和long類型,那麼設計BigDecimal的目的便是替代浮點型float和雙精度型double了。正如它的兄弟BigInteger一般,BigDecimal不存在什麼數值範圍限制,無論是整數部分還是小數部分,只要你能寫得出來,BigDecimal就能表達出來,從此不必擔心基本數字類型的精度問題了。
既然同為大數字家族,BigDecimal的絕大部分用法就與BigInteger保持一致,像add方法、subtract方法、abs方法、pow方法等等直接拿來便是,這裡不再重覆啰嗦了,且看下麵BigDecimal的方法調用代碼:
// 生成一個指定數值的大小數變數 BigDecimal sevenAndHalf = BigDecimal.valueOf(7.5); BigDecimal three = BigDecimal.valueOf(3); // add方法用來替代加法運算符“+” BigDecimal sum = sevenAndHalf.add(three); System.out.println("sum="+sum); // subtract方法用來替代減法運算符“-” BigDecimal sub = sevenAndHalf.subtract(three); System.out.println("sub="+sub); // multiply方法用來替代乘法運算符“*” BigDecimal mul = sevenAndHalf.multiply(three); System.out.println("mul="+mul); // divide方法用來替代除法運算符“/” BigDecimal div = sevenAndHalf.divide(three); System.out.println("div="+div); // remainder方法用來替代取餘數運算符“%” BigDecimal remainder = sevenAndHalf.remainder(three); System.out.println("remainder="+remainder); // negate方法用來替代負號運算符“-” BigDecimal neg = sevenAndHalf.negate(); System.out.println("neg="+neg); // abs方法用來替代數學庫函數Math.abs BigDecimal abs = sevenAndHalf.abs(); System.out.println("abs="+abs); // pow方法用來替代數學庫函數Math.pow BigDecimal pow = sevenAndHalf.pow(2); System.out.println("pow="+pow);
哇噻,難道這麼容易就學會使用BigDecimal了嗎?仔細看上面的例子代碼,被除數是7.5,除數是3,二者相除得到的商為2.5。註意這是除得盡的情況,倘若換個除不盡的情況,例如把除數改成7,7.5除以7結果理應得到一個無限迴圈小數。可要是運行以下的測試代碼,沒想到程式竟然運行異常,未能列印那個值為無限迴圈小數的商。
// 只有一個輸入參數的divide方法,要求被除數能夠被除數除得盡。 // 倘若除不盡,也就是商為無限迴圈小數,則程式會異常退出, // 報錯“Non-terminating decimal expansion; no exact representable decimal result.” BigDecimal seven = BigDecimal.valueOf(7); BigDecimal divTest = sevenAndHalf.divide(seven); System.out.println("divTest="+divTest);
雖說大小數能夠表示任意範圍的小數,但必須是個有限的範圍,而不能是無限的範圍。由於記憶體容量是有限的,一個無限迴圈小數寫出來都寫不完,要是放到記憶體就需要無限大小的記憶體,因此為了讓記憶體能夠放得下無限迴圈小數,只好給該小數指定需要保留的小數位數,也就意味著BigDecimal表示無限迴圈小數時還是有精度要求的。
除了規定小數部分的保留位數,還需明確多餘部分的數字是直接捨棄還是四捨五入?這樣對於無限迴圈小數來說,除法運算的divide方法需要三個輸入參數,包括除數、需要保留的小數位數、多餘數字的舍入規則。BigDecimal提供的數字舍入規則主要有下列幾種:
ROUND_CEILING:往數值較小的方向取整,類似於Math庫的ceiling函數。
ROUND_FLOOR:往數值較大的方向取整,類似於Math庫的floor函數。
ROUND_HALF_UP:四捨五入取整,若多餘的數字等於.5,則前一位進1,類似於Math庫的round函數。
ROUND_HALF_DOWN:類似四捨五入取整,區別在於:若多餘的數字等於.5,則直接捨棄。
ROUND_HALF_EVEN:如果保留位數的末尾為奇數,則按照ROUND_HALF_UP方式取整。如果保留位數的末尾為偶數,則按照ROUND_HALF_DOWN方式取整。
由上述規則可知,通常情況下的四捨五入應當採取ROUND_HALF_UP方式。於是重新指定了小數精度和舍入規則,改寫後大小數的除法運算代碼示例如下:
BigDecimal one = BigDecimal.valueOf(100); BigDecimal three = BigDecimal.valueOf(3); // 大小數的除法運算,小數點後面保留64位,其中最後一位做四捨五入 BigDecimal div = one.divide(three, 64, BigDecimal.ROUND_HALF_UP); System.out.println("div="+div);
運行修改後的除法代碼,控制台列印的日誌結果見下:
div=33.3333333333333333333333333333333333333333333333333333333333333333
可見此時除法計算正常工作,並且結果值的小數部分確實保留到了64位。
上述帶三個輸入參數的divide方法固然實現了符合精度的除法運算,但若代碼存在多處調用divide方法,便意味著該方法後面的精度規則“64, BigDecimal.ROUND_HALF_UP”在每處調用的地方都會出現,這樣不但造成代碼重覆,而且要是變更精度規則還得改動多處。為此Java又提供了工具MathContext,利用該工具可事先指定包含小數精度和舍入規則在內的精度規則,然後把設置好的工具對象傳給divide方法就好了。下麵是使用MathContext工具輔助除法運算的代碼例子:
// 利用工具MathContext,可以把divide方法的輸入參數減少為兩個 MathContext mc = new MathContext(64, RoundingMode.HALF_UP); BigDecimal divByMC = one.divide(three, mc); System.out.println("divByMC="+divByMC);
在大小數的除法中引入工具MathContext,至少有兩個好處,其一為:只要定義一次,即可多處使用;其二為:若要變更精度規則,只需修改一個地方。
更多Java技術文章參見《Java開發筆記(序)章節目錄》