天涯明月刀ol|天涯明月刀迅雷下载
  1. 當前所在位置:
  2. 首頁
  3. 全民捕魚

為什么要使用BigDecimal類型進行高精度運算

2018-12-28 admin
  在公司,每次進行記賬,或者算錢的時候,都會調用一個統一處理的類,名字叫ArithUntil,查了一下Arith就是數學、運算的意思,那么,為什么不直接用+ - * / 來進行對于數字類型數據的運算呢?(Ps.存儲數據的時候有的用的int類型比如打折,但是存儲金額的時候用的Decimal類型)。
 
       
 
為什么要使用BigDecimal類型進行高精度運算一、探索
 
 
        探究其原因,從網上查了查,查到了一個有意思的東西,就是直接輸出java運算某些數字的結果,卻發現會在千萬甚至億萬的小數級上出現錯誤,并且都是在浮點運算的時候才會出現的一些小誤差的結果,這里有兩組,大家可以跑一下試試:
 
        
 
public static void main(String[] args) {
System.out.println(1.0-0.8);
System.out.println(1.1+0.8);
System.out.println(1.1*0.9);
System.out.println(606.3/3000);
}
    System.out.println(0.05 + 0.01);
    System.out.println(1.0 - 0.42);
    System.out.println(4.015 * 100);
    System.out.println(123.3 / 100);
第一個的結果
 
第二個的結果
 
        這個是什么原因呢?原因在于我們的計算機是二進制的。浮點數沒有辦法是用二進制進行精確表示。我們的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。
 
比如我在網上查了一下十進制小數的二進制表示方法:
 
https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html
 
由此可以得到,帶小數的數字,如4.015其實是分成了兩個部分表示,分別是整數部分4,和0.015部分,因為運算后的小數部分按照文中所述方法不能完全的變為由0,1表示的二進制數,所以cpu對于數值進行了舍棄,得到 了如上所示的值。
 
然后找到了一句特別官方的話:
 
 其實java的float只能用來進行科學計算或工程計算,在大多數的商業計算中,一般采用java.math.BigDecimal類來進行精確計算。
 
 
為什么要使用BigDecimal類型進行高精度運算二、實際用法
 
 
        下面奉上通用的操作類(網上的好像都是這樣寫的):
 
import java.math.BigDecimal;   
/**  
* 由于Java的簡單類型不能夠精確的對浮點數進行運算,這個工具類提供精  
* 確的浮點數運算,包括加減乘除和四舍五入。  
*/  
public class ArithUntil{ //默認除法運算精度   
private static final int DEF_DIV_SCALE = 10; //這個類不能實例化   
private ArithUntil(){   
}   
/**  
* 提供精確的加法運算。  
* @param v1 被加數  
* @param v2 加數  
* @return 兩個參數的和  
*/  
public static double add(double v1,double v2){   
BigDecimal b1 = new BigDecimal(Double.toString(v1));   
BigDecimal b2 = new BigDecimal(Double.toString(v2));   
return b1.add(b2).doubleValue();   
}   
/**  
* 提供精確的減法運算。  
* @param v1 被減數  
* @param v2 減數  
* @return 兩個參數的差  
*/  
public static double sub(double v1,double v2){   
BigDecimal b1 = new BigDecimal(Double.toString(v1));   
BigDecimal b2 = new BigDecimal(Double.toString(v2));   
return b1.subtract(b2).doubleValue();   
}   
/**  
* 提供精確的乘法運算。  
* @param v1 被乘數  
* @param v2 乘數  
* @return 兩個參數的積  
*/  
public static double mul(double v1,double v2){   
BigDecimal b1 = new BigDecimal(Double.toString(v1));   
BigDecimal b2 = new BigDecimal(Double.toString(v2));   
return b1.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 new IllegalArgumentException(   
"The scale must be a positive integer or zero");   
}   
BigDecimal b1 = new BigDecimal(Double.toString(v1));   
BigDecimal b2 = new BigDecimal(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 new IllegalArgumentException("The scale must be a positive integer or zero");   
}   
BigDecimal b = new BigDecimal(Double.toString(v));   
BigDecimal one = new BigDecimal("1");   
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();   
}   
};
總結一下,一般步驟為:
 
1)用float或者double變量構建BigDecimal對象。
 
2)通過調用BigDecimal的加,減,乘,除等相應的方法進行算術運算。
 
3)把BigDecimal對象轉換成float,double,int等類型。
 
 一般來說,可以使用BigDecimal的構造方法或者靜態方法的valueOf()方法把基本類型的變量構建成BigDecimal對象。
 
 
為什么要使用BigDecimal類型進行高精度運算三、BigDecimal簡介
 
 
         BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。如果為零或正數,則標度是小數點后的位數。如果為負數,則將該數的非標度值乘以 10 的負scale 次冪。因此,BigDecimal表示的數值是(unscaledValue × 10-scale)。
 
部分源碼:
 
1、valueOf方法
 
    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.
       return new BigDecimal(Double.toString(val));
    }
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);為什么要使用BigDecimal類型進行高精度運算
 
捕魚駕到 天涯明月刀ol