1.引言
?
????????借用《Effactive Java》這本書中的話,float和double類型的主要設計目標是為了科學計算和工程計算。他們執行二進制浮點運算,這是為了在廣域數值范圍上提供較為精確的快速近似計算而精心設計的。然而,它們沒有提供完全精確的結果,所以不應該被用于要求精確結果的場合。但是,商業計算往往要求結果精確,這時候BigDecimal就派上大用場啦。
?
?
2.BigDecimal簡介
?
??????? BigDecimal?由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。如果為零或正數,則標度是小數點后的位數。如果為負數,則將該數的非標度值乘以 10 的負scale 次冪。因此,BigDecimal表示的數值是(unscaledValue × 10-scale)。
?
?
3.測試代碼
3.1構造函數(主要測試參數類型為double和String的兩個常用構造函數)
?
?????? BigDecimal aDouble =newBigDecimal(1.22);
?
??????? System.out.println("construct witha double value: " + aDouble);
?
??????? BigDecimal aString =newBigDecimal("1.22");
?
???????? System.out.println("constructwith a String value: " + aString);
?
????????你認為輸出結果會是什么呢?如果你沒有認為第一個會輸出1.22,那么恭喜你答對了,輸出結果如下:
?
???????? construct with adoublevalue:1.2199999999999999733546474089962430298328399658203125
?
???????? construct with a String value: 1.22
?
??????? JDK的描述:1、參數類型為double的構造方法的結果有一定的不可預知性。有人可能認為在Java中寫入newBigDecimal(0.1)所創建的BigDecimal正好等于 0.1(非標度值 1,其標度為 1),但是它實際上等于0.1000000000000000055511151231257827021181583404541015625。這是因為0.1無法準確地表示為 double(或者說對于該情況,不能表示為任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等于 0.1(雖然表面上等于該值)。
?
??????? 2、另一方面,String 構造方法是完全可預知的:寫入newBigDecimal("0.1") 將創建一個 BigDecimal,它正好等于預期的 0.1。因此,比較而言,通常建議優先使用String構造方法。
?
??????? 3、當double必須用作BigDecimal的源時,請注意,此構造方法提供了一個準確轉換;它不提供與以下操作相同的結果:先使用Double.toString(double)方法,然后使用BigDecimal(String)構造方法,將double轉換為String。要獲取該結果,請使用static valueOf(double)方法。
3.2?加法操作
?
??????? BigDecimal a =newBigDecimal("1.22");
?
??????? System.out.println("construct witha String value: " + a);
?
??????? BigDecimal b =newBigDecimal("2.22");
?
??????? a.add(b);
?
??????? System.out.println("aplus b is :" + a);
?
????????我們很容易會認為會輸出:
?
??????? construct with a Stringvalue: 1.22
?
??????? a plus b is :3.44
?
????????但實際上a plus b is : 1.22
4.源碼分析
4.1 valueOf(doubleval)方法
?
??? public??static BigDecimal valueOf(double val) {
?
?????? // Reminder: a zero double returns'0.0', so we cannotfastpath
?
?????? // to use the constant ZERO. This mightbe important enough to
?
?????? // justify a factory approach, a cache,or a few private
?
?????? // constants, later.
?
?????? returnnewBigDecimal(Double.toString(val));//見3.1關于JDK描述的第三點
?
??? }
4.2 add(BigDecimal augend)方法
?
????? public BigDecimal?? add(BigDecimal augend) {
?
????????? long xs =this.intCompact; //整型數字表示的BigDecimal,例a的intCompact值為122
?
????????? long ys = augend.intCompact;//同上
?
????????? BigInteger fst = (this.intCompact!=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intVal為BigDecimal的一個BigInteger類型的屬性
?
????? ????BigInteger snd =(augend.intCompact!=INFLATED) ?null : augend.intVal;
?
????????? int rscale =this.scale;//小數位數
?
?
?
????????? long sdiff = (long)rscale -augend.scale;//小數位數之差
?
????????? if (sdiff != 0) {//取小數位數多的為結果的小數位數
?
????????????? if (sdiff < 0) {
?
???????????????? int raise =checkScale(-sdiff);
?
???????????????? rscale =augend.scale;
?
???????????????? if (xs ==INFLATED ||
?
???????????????????? (xs=longMultiplyPowerTen(xs,raise)) ==INFLATED)
?
???????????????????? fst=bigMultiplyPowerTen(raise);
?
??????????????? }else {
?
?????????????????? int raise=augend.checkScale(sdiff);
?
?????????????????? if (ys ==INFLATED ||(ys=longMultiplyPowerTen(ys,raise)) ==INFLATED)
?
?????????????????????? snd =augend.bigMultiplyPowerTen(raise);
?
?????????????? }
?
????????? }
?
????????? if (xs !=INFLATED && ys!=INFLATED) {
?
????????????? long sum = xs + ys;
?
????????????? if ( (((sum ^ xs) &(sum ^ys))) >= 0L)//判斷有無溢出
?
???????????????? returnBigDecimal.valueOf(sum,rscale);//返回使用BigDecimal的靜態工廠方法得到的BigDecimal實例
?
??????????}
?
?????????? if (fst ==null)
?
?????????????? fst=BigInteger.valueOf(xs);//BigInteger的靜態工廠方法
?
?????????? if (snd ==null)
?
?????????????? snd =BigInteger.valueOf(ys);
?
?????????? BigInteger sum =fst.add(snd);
?
?????????? return (fst.signum == snd.signum)?new BigDecimal(sum,INFLATED, rscale, 0) :
?
????????????? newBigDecimal(sum,compactValFor(sum),rscale, 0);//返回通過其他構造方法得到的BigDecimal對象
?
?????? }
?
?
?
????????以上只是對加法源碼的分析,減乘除其實最終都返回的是一個新的BigDecimal對象,因為BigInteger與BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以a.add(b);雖然做了加法操作,但是a并沒有保存加操作后的值,正確的用法應該是a=a.add(b);
?
?
5.java的四舍五入詳解
?
四舍五入是我們小學的數學問題,這個問題對于我們程序猿來說就類似于1到10的加減乘除那么簡單了。在講解之間我們先看如下一個經典的案例:
?
[java] view plaincopy
?
??? public static void main(String[] args){?
??????????? System.out.println("12.5的四舍五入值:" + Math.round(12.5));?
??????????? System.out.println("-12.5的四舍五入值:" + Math.round(-12.5));?
??????? }?
??? Output:?
??? 12.5的四舍五入值:13?
??? -12.5的四舍五入值:-12?
?
??????這是四舍五入的經典案例,也是我們參加校招時候經常會遇到的(貌似我參加筆試的時候遇到過好多次)。從這兒結果中我們發現這兩個絕對值相同的數字,為何近似值會不同呢?其實這與Math.round采用的四舍五入規則來決定。
?
??????四舍五入其實在金融方面運用的非常多,尤其是銀行的利息。我們都知道銀行的盈利渠道主要是利息差,它從儲戶手里收集資金,然后放貸出去,期間產生的利息差就是銀行所獲得的利潤。如果我們采用平常四舍五入的規則話,這里采用每10筆存款利息計算作為模型,如下:
?
??????四舍:0.000、0.001、0.002、0.003、0.004。這些舍的都是銀行賺的錢。
?
??????五入:0.005、0.006、0.007、0.008、0.009。這些入的都是銀行虧的錢,分別為:0.005、0.004、.003、0.002、0.001。
?
??????所以對于銀行來說它的盈利應該是0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 -0.002 - 0.001 = -0.005。從結果中可以看出每10筆的利息銀行可能就會損失0.005元,千萬別小看這個數字,這對于銀行來說就是一筆非常大的損失。面對這個問題就產生了如下的銀行家涉入法了。該算法是由美國銀行家提出了,主要用于修正采用上面四舍五入規則而產生的誤差。如下:
?
??????舍去位的數值小于5時,直接舍去。
?
??????舍去位的數值大于5時,進位后舍去。
?
??????當舍去位的數值等于5時,若5后面還有其他非0數值,則進位后舍去,若5后面是0時,則根據5前一位數的奇偶性來判斷,奇數進位,偶數舍去。
?
??????對于上面的規則我們舉例說明
?
???????? 11.556 = 11.56 ------六入
?
???????? 11.554 = 11.55 -----四舍
?
???????? 11.5551 = 11.56 -----五后有數進位
?
???????? 11.545 = 11.54 -----五后無數,若前位為偶數應舍去
?
???????? 11.555 = 11.56 -----五后無數,若前位為奇數應進位
?
??????下面實例是使用銀行家舍入法:
?
[java] view plaincopy
?
??? public static void main(String[] args){?
??????????? BigDecimal d = newBigDecimal(100000);????? //存款?
??????????? BigDecimal r = newBigDecimal(0.001875*3);?? //利息?
??????????? BigDecimal i =d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);???? //使用銀行家算法??
?????????????
??????????? System.out.println("季利息是:"+i);?
??????????? }?
??? Output:?
????季利息是:562.50?
?
??????在上面簡單地介紹了銀行家舍入法,目前java支持7中舍入法:
?
??????? 1、ROUND_UP:遠離零方向舍入。向絕對值最大的方向舍入,只要舍棄位非0即進位。
?
??????? 2、ROUND_DOWN:趨向零方向舍入。向絕對值最小的方向輸入,所有的位都要舍棄,不存在進位情況。
?
??????? 3、ROUND_CEILING:向正無窮方向舍入。向正最大方向靠攏。若是正數,舍入行為類似于ROUND_UP,若為負數,舍入行為類似于ROUND_DOWN。Math.round()方法就是使用的此模式。
?
??????? 4、ROUND_FLOOR:向負無窮方向舍入。向負無窮方向靠攏。若是正數,舍入行為類似于ROUND_DOWN;若為負數,舍入行為類似于ROUND_UP。
?
??????? 5、HALF_UP:最近數字舍入(5進)。這是我們最經典的四舍五入。
?
??????? 6、HALF_DOWN:最近數字舍入(5舍)。在這里5是要舍棄的。
?
??????? 7、HAIF_EVEN:銀行家舍入法。
?
??????提到四舍五入那么保留位就必不可少了,在java運算中我們可以使用多種方式來實現保留位。
?保留位
?
?????方法一:四舍五入
?
[java] view plaincopy
?
??? double??f?? =?? 111231.5585;?
??? BigDecimal??b?? =?? new??BigDecimal(f);?
??? double??f1?? =?? b.setScale(2,?? RoundingMode.HALF_UP).doubleValue();?
?
??????在這里使用BigDecimal ,并且采用setScale方法來設置精確度,同時使用RoundingMode.HALF_UP表示使用最近數字舍入法則來近似計算。在這里我們可以看出BigDecimal和四舍五入是絕妙的搭配。
?
??????方式二:
[java] view plaincopy
?
??? java.text.DecimalFormat?? df??=new?? java.text.DecimalFormat(”#.00″);?
??? df.format(你要格式化的數字);?
?
??????例:new java.text.DecimalFormat(”#.00″).format(3.1415926)
?
????? #.00?表示兩位小數 #.0000四位小數 以此類推…
?
?????方式三:
[java] view plaincopy
?
??? double d = 3.1415926;?
?????
??? String result = String.format(”%.2f”);?
?????
??? %.2f %.?表示 小數點前任意位數?? 2 表示兩位小數 格式后的結果為f 表示浮點型。?
?
??????方式四:
?
??????此外如果使用struts標簽做輸出的話,有個format屬性,設置為format="0.00"就是保留兩位小數
?
??????例如:
?
[java] view plaincopy
?
??? <bean:write name="entity"property="dkhAFSumPl"?format="0.00" />?
?????
????或者?
?????
??? <fmt:formatNumbertype="number" value="${10000.22/100}"maxFractionDigits="0"/>?
?????
??? maxFractionDigits表示保留的位數
?
6.總結
?
??????? (1)商業計算使用BigDecimal。
?
???????(2)盡量使用參數類型為String的構造函數。
?
??????? (3) BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以在做加減乘除運算時千萬要保存操作后的值。
?
??????? (4)我們往往容易忽略JDK底層的一些實現細節,導致出現錯誤,需要多加注意。
?
7.封裝Arith類
[java] view plain copy
?
??? package lj.basic;?
?????
??? import java.math.BigDecimal;?
?????
??? public class Arith?
?????
??? {?
?????
??????? private static final int DEF_DIV_SCALE= 10;?
?????
??????? private Arith()?
?????
??????? {?
?????
??????? }?
?????
??????? /**
???????? *?
??????? ?*?
???????? *?
???????? *?提供精確的加法運算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被加數
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????加數
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數的和
???????? */?
?????
??????? public static double add(double v1,double v2)?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.add(b2).doubleValue();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的減法運算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被減數
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????減數
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數的差
???????? */?
?????
??????? public static double sub(double v1,double v2) ?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.subtract(b2).doubleValue();?
?????
??????? }?
?????
???????/**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的乘法運算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被乘數
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????乘數
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數的積
???????? */?
?????
??????? public static double mul(double v1,double v2)?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.multiply(b2).doubleValue();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供(相對)精確的除法運算,當發生除不盡的情況時,精確到
???????? *?
???????? *?
???????? *?
???????? *?小數點以后10位,以后的數字四舍五入。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被除數
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????除數
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數的商
???????? */?
?????
??????? public static double div(double v1,double v2)?
?????
??????? {?
?????
??????????? return div(v1, v2,DEF_DIV_SCALE);?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指
???????? *?
???????? *?
???????? *?
???????? *?定精度,以后的數字四舍五入。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被除數
???????? *?
???????? *?
????????*?
???????? * @param v2
???????? *????????????除數
???????? *?
???????? *?
???????? *?
???????? * @param scale
???????? *????????????表示表示需要精確到小數點以后幾位。
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數的商
???????? */?
?????
??????? public static double div(double v1,double v2, int scale)?
?????
??????? {?
?????
??????????? if (scale < 0)?
?????
??????????? {?
?????
??????????????? throw newIllegalArgumentException(?
??????????????????????? "The scale must bea positive integer or zero");?
?????
??????????? }?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? return b1.divide(b2, scale,BigDecimal.ROUND_HALF_UP).doubleValue();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的小數位四舍五入處理。
???????? *?
???????? *?
???????? *?
???????? * @param v
???????? *????????????需要四舍五入的數字
???????? *?
???????? *?
???????? *?
???????? * @param scale
???????? *????????????小數點后保留幾位
???????? *?
???????? *?
???????? *?
???????? * @return?四舍五入后的結果
???????? */?
?????
??????? public static double round(double v,int scale) {?
?????
??????????? if (scale < 0)?
?????
??????????? {?
?????
??????????????? throw newIllegalArgumentException(?
??????????????????????? "The scale must bea positive integer or zero");?
?????
??????????? }?
?????
??????????? BigDecimal b = newBigDecimal(Double.toString(v));?
?????
??????????? BigDecimal one = newBigDecimal("1");?
?????
??????????? return b.divide(one, scale,BigDecimal.ROUND_HALF_UP).doubleValue();?
?????
??????? }?
?????
??? }
文章出自http://blog.csdn.net/liujian928730/article/details/50542534