1.引言java
借用《Effactive Java》這本書中的話,float和double類型的主要設計目標是爲了科學計算和工程計算。他們執行二進制浮點運算,這是爲了在廣域數值範圍上提供較爲精確的快速近似計算而精心設計的。然而,它們沒有提供徹底精確的結果,因此不該該被用於要求精確結果的場合。可是,商業計算每每要求結果精確,這時候BigDecimal就派上大用場啦。git
2.BigDecimal簡介算法
BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。若是爲零或正數,則標度是小數點後的位數。若是爲負數,則將該數的非標度值乘以 10 的負scale 次冪。所以,BigDecimal表示的數值是(unscaledValue × 10-scale)。app
3.測試代碼ide
3.1構造函數(主要測試參數類型爲double和String的兩個經常使用構造函數)函數
BigDecimal aDouble =newBigDecimal(1.22);源碼分析
System.out.println("construct witha double value: " + aDouble);測試
BigDecimal aString =newBigDecimal("1.22");this
System.out.println("constructwith a String value: " + aString);spa
你認爲輸出結果會是什麼呢?若是你沒有認爲第一個會輸出1.22,那麼恭喜你答對了,輸出結果以下:
construct with adoublevalue:1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
JDK的描述:一、參數類型爲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.00一、0.00二、0.00三、0.004。這些舍的都是銀行賺的錢。
五入:0.00五、0.00六、0.00七、0.00八、0.009。這些入的都是銀行虧的錢,分別爲:0.00五、0.00四、.00三、0.00二、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