Java開發筆記(三十)大小數BigDecimal

前面介紹的BigInteger只能表達任意整數,但不能表達小數,要想表達任意小數,還需專門的大小數類型BigDecimal。若是說設計BigInteger的目的是替代int和long類型,那麼設計BigDecimal的目的即是替代浮點型float和雙精度型double了。正如它的兄弟BigInteger通常,BigDecimal不存在什麼數值範圍限制,不管是整數部分仍是小數部分,只要你能寫得出來,BigDecimal就能表達出來,今後沒必要擔憂基本數字類型的精度問題了。
既然同爲大數字家族,BigDecimal的絕大部分用法就與BigInteger保持一致,像add方法、subtract方法、abs方法、pow方法等等直接拿來即是,這裏再也不重複囉嗦了,且看下面BigDecimal的方法調用代碼:html

		// 生成一個指定數值的大小數變量
		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結果理應獲得一個無限循環小數。可要是運行如下的測試代碼,沒想到程序居然運行異常,未能打印那個值爲無限循環小數的商。java

		// 只有一個輸入參數的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方式。因而從新指定了小數精度和舍入規則,改寫後大小數的除法運算代碼示例以下:ide

		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開發筆記(序)章節目錄設計

相關文章
相關標籤/搜索