1.引言 借用《Effactive Java》這本書中的話,float和double類型的主要設計目標是爲了科學計算和工程計算。他們執行二進制浮點運算,這是爲了在廣域數值範圍上提供較爲精確的快速近似計算而精心設計的。然而,它們沒有提供徹底精確的結果,因此不該該被用於要求精確結果的場合。可是,商業計算每每要求結果精確,這時候BigDecimal就派上大用場啦。java
2.BigDecimal簡介 BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。若是爲零或正數,則標度是小數點後的位數。若是爲負數,則將該數的非標度值乘以 10 的負scale 次冪。所以,BigDecimal表示的數值是(unscaledValue × 10-scale)。git
3.測試代碼app
3.1構造函數(主要測試參數類型爲double和String的兩個經常使用構造函數)ide
BigDecimal aDouble =new BigDecimal(1.22); System.out.println("construct with a double value: " + aDouble); BigDecimal aString = new BigDecimal("1.22"); System.out.println("construct with a String value: " + aString);
你認爲輸出結果會是什麼呢?若是你沒有認爲第一個會輸出1.22,那麼恭喜你答對了,輸出結果以下:函數
construct with a doublevalue:1.2199999999999999733546474089962430298328399658203125 construct with a String value: 1.22
JDK的描述:工具
3.2 加法操做源碼分析
BigDecimal a =new BigDecimal("1.22"); System.out.println("construct with a String value: " + a); BigDecimal b =new BigDecimal("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 might be important enough to // justify a factory approach, a cache, or a few private // constants, later. returnnew BigDecimal(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)//判斷有無溢出 return BigDecimal.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) : new BigDecimal(sum,compactValFor(sum),rscale, 0);//返回經過其餘構造方法獲得的BigDecimal對象 }
以上只是對加法源碼的分析,減乘除其實最終都返回的是一個新的BigDecimal對象,由於BigInteger與BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,因此a.add(b);雖然作了加法操做,可是a並無保存加操做後的值,正確的用法應該是a=a.add(b);this
5.總結設計
** (1)商業計算使用BigDecimal。
(2)儘可能使用參數類型爲String的構造函數。
(3) BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,因此在作加減乘除運算時千萬要保存操做後的值。
(4)咱們每每容易忽略JDK底層的一些實現細節,致使出現錯誤,須要多加註意。**
BigDecimal 由任意精度的整數非標度值 和 32 位的整數標度 (scale) 組成。若是爲零或正數,則標度是小數點後的位數。若是爲負數,則將該數的非標度值乘以 10 的負 scale 次冪。所以,BigDecimal 表示的數值是 (unscaledValue × 10-scale)。
能夠處理任意長度的浮點數運算。
BigDecimal add(BigDecimal val) //BigDecimal 加法
BigDecimal subtract (BigDecimal val) //BigDecimal 減法
BigDecimal multiply (BigDecimal val) //BigDecimal 乘法
BigDecimal divide (BigDecimal val,RoundingMode mode) 除法
具體使用 計算:
加: a.add(b);
減: a.subtract(b);
乘: a.multiply(b);
除: a.divide(b,2);//2爲精度取值
** 除法細解:
//注意如下相除會拋出異常,緣由: 經過BigDecimal的divide方法進行除法時當不整除,出現無限循環小數時,就會拋異常
//BigDecimal divideBg = a.divide(b);
//解決方法是:設置精確度;就是給divide設置精確的小數點
divide(xxxxx,2, BigDecimal.ROUND_HALF_EVEN)
//其中的第二個參數表示的是:保留小數點以後多少位 **
BigDecimal不整除拋出的異常,請設置精確度! Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(BigDecimal.java:1278) at main.Main.main(Main.java:41)
下面咱們來看看除法的詳細說明: divide(BigDecimal divisor, int scale, introundingMode)
BigDecimal的setScale方法
BigDecimal.setScale()
方法用於格式化小數點
表示保留一位小數,默認用四捨五入方式 setScale(1)
直接刪除多餘的小數位,如2.35會變成2.3 setScale(1,BigDecimal.ROUND_DOWN)
進位處理,2.35變成2.4 setScale(1,BigDecimal.ROUND_UP)
四捨五入,2.35變成2.4 setScale(1,BigDecimal.ROUND_HALF_UP)
四捨五入,2.35變成2.3,若是是5則向下舍setScaler(1,BigDecimal.ROUND_HALF_DOWN)
**注意點一 **
scale指的是你小數點後的位數。 scale()就是BigDecimal類中的方法。如
BigDecimal b = new BigDecimal("123.456");
b.scale()返回的就是3
注意點二
roundingMode是小數的保留模式。它們都是BigDecimal中的常量字段,
有不少種,如 BigDecimal.ROUND_HALF_UP表示的就是4舍5入
**注意點三 **
divide(BigDecimal divisor, int scale, introundingMode)的意思是說: 我用一個BigDecimal對象除以divisor後的結果,而且要求這個結果保留有scale個小數位,roundingMode表示的就是保留模式是什麼,是四捨五入啊仍是其它的
BigDecimal aa = new BigDecimal(135.95 );
BigDecimal bb=new BigDecimal("100" );
BigDecimal result=aa.multiply(bb); //作加法
**3.java中 BigDecimal類型的能夠轉換到double類型: **
用 變量.doubleValue();函數 便可將 BigDecimal 類型數據 轉化爲 double類型!
**4.java BigDecimal比較大小 **
能夠經過BigDecimal的compareTo方法來進行比較。 返回的結果是int類型,-1表示小於,0是等於,1是大於。
看下面這個例子: BigDecimal a = new BigDecimal("1.00"); BigDecmial b = new BigDecimal(1);
想比較一下a和b的大小,通常都會用equals
System.out.println(a.equals(b)); 可是輸出結果是:false 緣由是:BigDecimal比較時,不只比較值,並且還比較精度?
if(a.compareTo(b)==0) 結果是true
比較大小能夠用 a.compareTo(b) 返回值 -1 小於 0 等於 1 大於
**5.BigDecimal取其中最大、最小值、絕對值、相反數: **
a.max (b) //比較取最大值
a.min(b) //比較取最小值
a.abs()//取最絕對值
a.negate()//取相反數
6.下面是注意 :
BigDecimal枚舉常量用法摘要 :
CEILING 向正無限大方向舍入的舍入模式。
DOWN 向零方向舍入的舍入模式。
FLOOR 向負無限大方向舍入的舍入模式。
HALF_DOWN 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向下舍入。
HALF_EVEN 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。
HALF_UP 向最接近數字方向舍入的舍入模式,若是與兩個相鄰數字的距離相等,則向上舍入。
UNNECESSARY 用於斷言請求的操做具備精確結果的舍入模式,所以不須要舍入。
UP 遠離零方向舍入的舍入模式。
**7.關於BigDecimal格式化 **
public String formatValue(Object value){ String content = null; if (value == null) { content = ""; } else { if(value instanceof BigDecimal){ //conver to fortmat String NumberFormat nf = NumberFormat.getInstance(); nf.setMinimumFractionDigits(2); nf.setMaximumFractionDigits(2); content = nf.format(value); }else{ content = String.valueOf(value); } } return content; }
使用這樣一個方法能夠達到格式化的效果,其中value instanceof BigDecimal,表示的是字符類型是BigDecimal類型的時候執行,這裏的NumberFormat就表示字符類型,下面的兩句代碼就表示小數點後面的精確位數。
這裏還要提到NumberFormat的其餘兩個類型:
getCurrencyInstance(): 返回當前默認 環境的貨幣格式
CurrencyInstance(): 返回指定語言 環境的數字格式,通常是百分比格式
**8.在《Effective Java》這本書中也提到這個原則,float和double只能用來作科學計算或者是工程計算,在商業計算中咱們要用java.math.BigDecimal。 咱們若是須要精確計算,非要用String來夠造BigDecimal不可! **
下面的這個工具類是是轉載別人的,能夠經過個工具類實現小數的精確計算。
1import java.math.BigDecimal; 2/** *//** 3* 因爲Java的簡單類型不可以精確的對浮點數進行運算,這個工具類提供精 4* 確的浮點數運算,包括加減乘除和四捨五入。 5*/ 6public class Arith{ 7 //默認除法運算精度 8 private static final int DEF_DIV_SCALE = 10; 9 //這個類不能實例化 10 private Arith(){ 11 } 12 13 /** *//** 14 * 提供精確的加法運算。 15 * @param v1 被加數 16 * @param v2 加數 17 * @return 兩個參數的和 18 */ 19 public static double add(double v1,double v2){ 20 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 21 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 22 return b1.add(b2).doubleValue(); 23 } 24 /** *//** 25 * 提供精確的減法運算。 26 * @param v1 被減數 27 * @param v2 減數 28 * @return 兩個參數的差 29 */ 30 public static double sub(double v1,double v2){ 31 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 32 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 33 return b1.subtract(b2).doubleValue(); 34 } 35 /** *//** 36 * 提供精確的乘法運算。 37 * @param v1 被乘數 38 * @param v2 乘數 39 * @return 兩個參數的積 40 */ 41 public static double mul(double v1,double v2){ 42 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 43 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 44 return b1.multiply(b2).doubleValue(); 45 } 46 47 /** *//** 48 * 提供(相對)精確的除法運算,當發生除不盡的狀況時,精確到 49 * 小數點之後10位,之後的數字四捨五入。 50 * @param v1 被除數 51 * @param v2 除數 52 * @return 兩個參數的商 53 */ 54 public static double div(double v1,double v2){ 55 return div(v1,v2,DEF_DIV_SCALE); 56 } 57 58 /** *//** 59 * 提供(相對)精確的除法運算。當發生除不盡的狀況時,由scale參數指 60 * 定精度,之後的數字四捨五入。 61 * @param v1 被除數 62 * @param v2 除數 63 * @param scale 表示表示須要精確到小數點之後幾位。 64 * @return 兩個參數的商 65 */ 66 public static double div(double v1,double v2,int scale){ 67 if(scale<0){ 68 throw new IllegalArgumentException( 69 "The scale must be a positive integer or zero"); 70 } 71 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 72 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 73 return b1.divid(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue(); 74 } 75 76 /** *//** 77 * 提供精確的小數位四捨五入處理。 78 * @param v 須要四捨五入的數字 79 * @param scale 小數點後保留幾位 80 * @return 四捨五入後的結果 81 */ 82 public static double round(double v,int scale){ 83 if(scale<0){ 84 throw new IllegalArgumentException( 85 "The scale must be a positive integer or zero"); 86 } 87 BigDecimal b = new BigDecimal(Double.toString(v)); 88 BigDecimal one = new BigDecimal("1"); 89 return b.divid(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue(); 90 } 91 }