BigDecimal四舍五入与保留位
1.引言
?
????????借用《Effactive Java》這本書中的話,float和double類型的主要設(shè)計(jì)目標(biāo)是為了科學(xué)計(jì)算和工程計(jì)算。他們執(zhí)行二進(jìn)制浮點(diǎn)運(yùn)算,這是為了在廣域數(shù)值范圍上提供較為精確的快速近似計(jì)算而精心設(shè)計(jì)的。然而,它們沒有提供完全精確的結(jié)果,所以不應(yīng)該被用于要求精確結(jié)果的場合。但是,商業(yè)計(jì)算往往要求結(jié)果精確,這時候BigDecimal就派上大用場啦。
?
?
2.BigDecimal簡介
?
??????? BigDecimal?由任意精度的整數(shù)非標(biāo)度值 和32 位的整數(shù)標(biāo)度 (scale) 組成。如果為零或正數(shù),則標(biāo)度是小數(shù)點(diǎn)后的位數(shù)。如果為負(fù)數(shù),則將該數(shù)的非標(biāo)度值乘以 10 的負(fù)scale 次冪。因此,BigDecimal表示的數(shù)值是(unscaledValue × 10-scale)。
?
?
3.測試代碼
3.1構(gòu)造函數(shù)(主要測試參數(shù)類型為double和String的兩個常用構(gòu)造函數(shù))
?
?????? 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);
?
????????你認(rèn)為輸出結(jié)果會是什么呢?如果你沒有認(rèn)為第一個會輸出1.22,那么恭喜你答對了,輸出結(jié)果如下:
?
???????? construct with adoublevalue:1.2199999999999999733546474089962430298328399658203125
?
???????? construct with a String value: 1.22
?
??????? JDK的描述:1、參數(shù)類型為double的構(gòu)造方法的結(jié)果有一定的不可預(yù)知性。有人可能認(rèn)為在Java中寫入newBigDecimal(0.1)所創(chuàng)建的BigDecimal正好等于 0.1(非標(biāo)度值 1,其標(biāo)度為 1),但是它實(shí)際上等于0.1000000000000000055511151231257827021181583404541015625。這是因?yàn)?.1無法準(zhǔn)確地表示為 double(或者說對于該情況,不能表示為任何有限長度的二進(jìn)制小數(shù))。這樣,傳入到構(gòu)造方法的值不會正好等于 0.1(雖然表面上等于該值)。
?
??????? 2、另一方面,String 構(gòu)造方法是完全可預(yù)知的:寫入newBigDecimal("0.1") 將創(chuàng)建一個 BigDecimal,它正好等于預(yù)期的 0.1。因此,比較而言,通常建議優(yōu)先使用String構(gòu)造方法。
?
??????? 3、當(dāng)double必須用作BigDecimal的源時,請注意,此構(gòu)造方法提供了一個準(zhǔn)確轉(zhuǎn)換;它不提供與以下操作相同的結(jié)果:先使用Double.toString(double)方法,然后使用BigDecimal(String)構(gòu)造方法,將double轉(zhuǎn)換為String。要獲取該結(jié)果,請使用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);
?
????????我們很容易會認(rèn)為會輸出:
?
??????? construct with a Stringvalue: 1.22
?
??????? a plus b is :3.44
?
????????但實(shí)際上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關(guān)于JDK描述的第三點(diǎn)
?
??? }
4.2 add(BigDecimal augend)方法
?
????? public BigDecimal?? add(BigDecimal augend) {
?
????????? long xs =this.intCompact; //整型數(shù)字表示的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;//小數(shù)位數(shù)
?
?
?
????????? long sdiff = (long)rscale -augend.scale;//小數(shù)位數(shù)之差
?
????????? if (sdiff != 0) {//取小數(shù)位數(shù)多的為結(jié)果的小數(shù)位數(shù)
?
????????????? 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的靜態(tài)工廠方法得到的BigDecimal實(shí)例
?
??????????}
?
?????????? if (fst ==null)
?
?????????????? fst=BigInteger.valueOf(xs);//BigInteger的靜態(tài)工廠方法
?
?????????? 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);//返回通過其他構(gòu)造方法得到的BigDecimal對象
?
?????? }
?
?
?
????????以上只是對加法源碼的分析,減乘除其實(shí)最終都返回的是一個新的BigDecimal對象,因?yàn)锽igInteger與BigDecimal都是不可變的(immutable)的,在進(jìn)行每一步運(yùn)算時,都會產(chǎn)生一個新的對象,所以a.add(b);雖然做了加法操作,但是a并沒有保存加操作后的值,正確的用法應(yīng)該是a=a.add(b);
?
?
5.java的四舍五入詳解
?
四舍五入是我們小學(xué)的數(shù)學(xué)問題,這個問題對于我們程序猿來說就類似于1到10的加減乘除那么簡單了。在講解之間我們先看如下一個經(jīng)典的案例:
?
[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?
?
??????這是四舍五入的經(jīng)典案例,也是我們參加校招時候經(jīng)常會遇到的(貌似我參加筆試的時候遇到過好多次)。從這兒結(jié)果中我們發(fā)現(xiàn)這兩個絕對值相同的數(shù)字,為何近似值會不同呢?其實(shí)這與Math.round采用的四舍五入規(guī)則來決定。
?
??????四舍五入其實(shí)在金融方面運(yùn)用的非常多,尤其是銀行的利息。我們都知道銀行的盈利渠道主要是利息差,它從儲戶手里收集資金,然后放貸出去,期間產(chǎn)生的利息差就是銀行所獲得的利潤。如果我們采用平常四舍五入的規(guī)則話,這里采用每10筆存款利息計(jì)算作為模型,如下:
?
??????四舍: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。
?
??????所以對于銀行來說它的盈利應(yīng)該是0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 -0.002 - 0.001 = -0.005。從結(jié)果中可以看出每10筆的利息銀行可能就會損失0.005元,千萬別小看這個數(shù)字,這對于銀行來說就是一筆非常大的損失。面對這個問題就產(chǎn)生了如下的銀行家涉入法了。該算法是由美國銀行家提出了,主要用于修正采用上面四舍五入規(guī)則而產(chǎn)生的誤差。如下:
?
??????舍去位的數(shù)值小于5時,直接舍去。
?
??????舍去位的數(shù)值大于5時,進(jìn)位后舍去。
?
??????當(dāng)舍去位的數(shù)值等于5時,若5后面還有其他非0數(shù)值,則進(jìn)位后舍去,若5后面是0時,則根據(jù)5前一位數(shù)的奇偶性來判斷,奇數(shù)進(jìn)位,偶數(shù)舍去。
?
??????對于上面的規(guī)則我們舉例說明
?
???????? 11.556 = 11.56 ------六入
?
???????? 11.554 = 11.55 -----四舍
?
???????? 11.5551 = 11.56 -----五后有數(shù)進(jìn)位
?
???????? 11.545 = 11.54 -----五后無數(shù),若前位為偶數(shù)應(yīng)舍去
?
???????? 11.555 = 11.56 -----五后無數(shù),若前位為奇數(shù)應(yīng)進(jìn)位
?
??????下面實(shí)例是使用銀行家舍入法:
?
[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:遠(yuǎn)離零方向舍入。向絕對值最大的方向舍入,只要舍棄位非0即進(jìn)位。
?
??????? 2、ROUND_DOWN:趨向零方向舍入。向絕對值最小的方向輸入,所有的位都要舍棄,不存在進(jìn)位情況。
?
??????? 3、ROUND_CEILING:向正無窮方向舍入。向正最大方向靠攏。若是正數(shù),舍入行為類似于ROUND_UP,若為負(fù)數(shù),舍入行為類似于ROUND_DOWN。Math.round()方法就是使用的此模式。
?
??????? 4、ROUND_FLOOR:向負(fù)無窮方向舍入。向負(fù)無窮方向靠攏。若是正數(shù),舍入行為類似于ROUND_DOWN;若為負(fù)數(shù),舍入行為類似于ROUND_UP。
?
??????? 5、HALF_UP:最近數(shù)字舍入(5進(jìn))。這是我們最經(jīng)典的四舍五入。
?
??????? 6、HALF_DOWN:最近數(shù)字舍入(5舍)。在這里5是要舍棄的。
?
??????? 7、HAIF_EVEN:銀行家舍入法。
?
??????提到四舍五入那么保留位就必不可少了,在java運(yùn)算中我們可以使用多種方式來實(shí)現(xiàn)保留位。
?保留位
?
?????方法一:四舍五入
?
[java] view plaincopy
?
??? double??f?? =?? 111231.5585;?
??? BigDecimal??b?? =?? new??BigDecimal(f);?
??? double??f1?? =?? b.setScale(2,?? RoundingMode.HALF_UP).doubleValue();?
?
??????在這里使用BigDecimal ,并且采用setScale方法來設(shè)置精確度,同時使用RoundingMode.HALF_UP表示使用最近數(shù)字舍入法則來近似計(jì)算。在這里我們可以看出BigDecimal和四舍五入是絕妙的搭配。
?
??????方式二:
[java] view plaincopy
?
??? java.text.DecimalFormat?? df??=new?? java.text.DecimalFormat(”#.00″);?
??? df.format(你要格式化的數(shù)字);?
?
??????例:new java.text.DecimalFormat(”#.00″).format(3.1415926)
?
????? #.00?表示兩位小數(shù) #.0000四位小數(shù) 以此類推…
?
?????方式三:
[java] view plaincopy
?
??? double d = 3.1415926;?
?????
??? String result = String.format(”%.2f”);?
?????
??? %.2f %.?表示 小數(shù)點(diǎn)前任意位數(shù)?? 2 表示兩位小數(shù) 格式后的結(jié)果為f 表示浮點(diǎn)型。?
?
??????方式四:
?
??????此外如果使用struts標(biāo)簽做輸出的話,有個format屬性,設(shè)置為format="0.00"就是保留兩位小數(shù)
?
??????例如:
?
[java] view plaincopy
?
??? <bean:write name="entity"property="dkhAFSumPl"?format="0.00" />?
?????
????或者?
?????
??? <fmt:formatNumbertype="number" value="${10000.22/100}"maxFractionDigits="0"/>?
?????
??? maxFractionDigits表示保留的位數(shù)
?
6.總結(jié)
?
??????? (1)商業(yè)計(jì)算使用BigDecimal。
?
???????(2)盡量使用參數(shù)類型為String的構(gòu)造函數(shù)。
?
??????? (3) BigDecimal都是不可變的(immutable)的,在進(jìn)行每一步運(yùn)算時,都會產(chǎn)生一個新的對象,所以在做加減乘除運(yùn)算時千萬要保存操作后的值。
?
??????? (4)我們往往容易忽略JDK底層的一些實(shí)現(xiàn)細(xì)節(jié),導(dǎo)致出現(xiàn)錯誤,需要多加注意。
?
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()?
?????
??????? {?
?????
??????? }?
?????
??????? /**
???????? *?
??????? ?*?
???????? *?
???????? *?提供精確的加法運(yùn)算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被加數(shù)
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????加數(shù)
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數(shù)的和
???????? */?
?????
??????? public static double add(double v1,double v2)?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.add(b2).doubleValue();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的減法運(yùn)算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被減數(shù)
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????減數(shù)
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數(shù)的差
???????? */?
?????
??????? public static double sub(double v1,double v2) ?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.subtract(b2).doubleValue();?
?????
??????? }?
?????
???????/**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的乘法運(yùn)算。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被乘數(shù)
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????乘數(shù)
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數(shù)的積
???????? */?
?????
??????? public static double mul(double v1,double v2)?
?????
??????? {?
?????
??????????? BigDecimal b1 = newBigDecimal(Double.toString(v1));?
?????
??????????? BigDecimal b2 = newBigDecimal(Double.toString(v2));?
?????
??????????? returnb1.multiply(b2).doubleValue();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供(相對)精確的除法運(yùn)算,當(dāng)發(fā)生除不盡的情況時,精確到
???????? *?
???????? *?
???????? *?
???????? *?小數(shù)點(diǎn)以后10位,以后的數(shù)字四舍五入。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被除數(shù)
???????? *?
???????? *?
???????? *?
???????? * @param v2
???????? *????????????除數(shù)
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數(shù)的商
???????? */?
?????
??????? public static double div(double v1,double v2)?
?????
??????? {?
?????
??????????? return div(v1, v2,DEF_DIV_SCALE);?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供(相對)精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時,由scale參數(shù)指
???????? *?
???????? *?
???????? *?
???????? *?定精度,以后的數(shù)字四舍五入。
???????? *?
???????? *?
???????? *?
???????? * @param v1
???????? *????????????被除數(shù)
???????? *?
???????? *?
????????*?
???????? * @param v2
???????? *????????????除數(shù)
???????? *?
???????? *?
???????? *?
???????? * @param scale
???????? *????????????表示表示需要精確到小數(shù)點(diǎn)以后幾位。
???????? *?
???????? *?
???????? *?
???????? * @return?兩個參數(shù)的商
???????? */?
?????
??????? 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();?
?????
??????? }?
?????
??????? /**
???????? *?
???????? *?
???????? *?
???????? *?提供精確的小數(shù)位四舍五入處理。
???????? *?
???????? *?
???????? *?
???????? * @param v
???????? *????????????需要四舍五入的數(shù)字
???????? *?
???????? *?
???????? *?
???????? * @param scale
???????? *????????????小數(shù)點(diǎn)后保留幾位
???????? *?
???????? *?
???????? *?
???????? * @return?四舍五入后的結(jié)果
???????? */?
?????
??????? 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
總結(jié)
以上是生活随笔為你收集整理的BigDecimal四舍五入与保留位的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言求1000以内完数
- 下一篇: shell脚本常用语句用法笔记