BigDecimal讲解

来源:互联网 发布:淘宝网 支付宝的功能 编辑:程序博客网 时间:2024/04/27 20:26

1.引言

        借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

2.BigDecimal简介

        BigDecimal由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)。

3.测试代码

3.1构造函数(主要测试参数类型为double和String的两个常用构造函数)

       BigDecimalaDouble =new BigDecimal(1.22);

        System.out.println("constructwith a double value: " + aDouble);

        BigDecimalaString = new BigDecimal("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 =new BigDecimal("1.22");

       System.out.println("constructwith 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: azero double returns '0.0', so we cannotfastpath

       // to use theconstant ZERO. This might be important enough to

       // justify afactory approach, a cache, or a few private

       // constants,later.

       returnnew BigDecimal(Double.toString(val));//见3.1关于JDK描述的第三点

    }

4.2 add(BigDecimalaugend)方法

     public BigDecimal   add(BigDecimal augend) {

          long xs =this.intCompact; //整型数字表示的BigDecimal,例a的intCompact值为122

          long ys = augend.intCompact;//同上

          BigIntegerfst = (this.intCompact !=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intVal为BigDecimal的一个BigInteger类型的属性

          BigIntegersnd =(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);

           BigIntegersum =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);

5.总结

       (1)商业计算使用BigDecimal。

       (2)尽量使用参数类型为String的构造函数。

       (3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。

       (4)我们往往容易忽略JDK底层的一些实现细节,导致出现错误,需要多加注意。

6.应用

(1)使用BigDecimal类进行计算

在使用BigDecimal类来进行计算的时候,主要分为以下步骤:

              1、用float或者double变量构建BigDecimal对象。

             2、通过调用BigDecimal的加,减,乘,除等相应的方法进行算术运算。

             3、把BigDecimal对象转换成floatdoubleint等类型。

          一般来说,可以使用BigDecimal的构造方法或者静态方法的valueOf()方法把基本类型的变量构建成BigDecimal对象(参数类型为double的构造方法的结果有一定的不可预知性)。

1 BigDecimal b1 =newBigDecimal(Double.toString(0.48));

2 BigDecimal b2 = BigDecimal.valueOf(0.48);

        对于常用的加,减,乘,除,BigDecimal类提供了相应的成员方法。

1 public BigDecimaladd(BigDecimal value);                       //加法

2 public BigDecimalsubtract(BigDecimal value);                  //减法

3 public BigDecimalmultiply(BigDecimal value);                  //乘法

4 public BigDecimaldivide(BigDecimal value);                    //除法


          
进行相应的计算后,我们可能需要将BigDecimal对象转换成相应的基本数据类型的变量,可以使用floatValue()doubleValue()等方法。

          下面是一个工具类,该工具类提供加,减,乘,除运算。

public class Arith {

    /**

     * 提供精确加法计算的add方法

     * @param value1 被加数

     * @param value2 加数

     * @return 两个参数的和

     */

    public static double add(doublevalue1,double value2){

        BigDecimal b1 = newBigDecimal(Double.valueOf(value1));

        BigDecimal b2 = newBigDecimal(Double.valueOf(value2));

        return b1.add(b2).doubleValue();

    }

   

    /**

     * 提供精确减法运算的sub方法

     * @param value1 被减数

     * @param value2 减数

     * @return 两个参数的差

     */

    public static double sub(doublevalue1,double value2){

        BigDecimal b1 = newBigDecimal(Double.valueOf(value1));

        BigDecimal b2 = newBigDecimal(Double.valueOf(value2));

        return b1.subtract(b2).doubleValue();

    }

   

    /**

     * 提供精确乘法运算的mul方法

     * @param value1 被乘数

     * @param value2 乘数

     * @return 两个参数的积

     */

    public static double mul(doublevalue1,double value2){

        BigDecimal b1 = newBigDecimal(Double.valueOf(value1));

        BigDecimal b2 = newBigDecimal(Double.valueOf(value2));

        return b1.multiply(b2).doubleValue();

    }

   

    /**

     * 提供精确的除法运算方法div

     * @param value1 被除数

     * @param value2 除数

     * @param scale 精确范围

     * @return 两个参数的商

     * @throws IllegalAccessException

     */

    public static double div(doublevalue1,double value2,int scale) throws IllegalAccessException{

        //如果精确范围小于0,抛出异常信息

        if(scale<0){        

            throw newIllegalAccessException("精确度不能小于0");

        }

        BigDecimal b1 = newBigDecimal(Double.valueOf(value1));

        BigDecimal b2 = newBigDecimal(Double.valueOf(value2));

        return b1.divide(b2,scale).doubleValue();   

    }

}

(2)格式化及例子

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。 
以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

publicstaticvoidmain(String[] args) {
    NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用 
    NumberFormat percent = NumberFormat.getPercentInstance();  //建立百分比格式化引用 
    percent.setMaximumFractionDigits(3); //百分比小数点最多3
 
    BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
    BigDecimal interestRate = new BigDecimal("0.008"); //利率   
    BigDecimal interest = loanAmount.multiply(interestRate); //相乘
 
    System.out.println("贷款金额:\t" + currency.format(loanAmount)); 
    System.out.println("利率:\t" + percent.format(interestRate)); 
    System.out.println("利息:\t" + currency.format(interest)); 
}

结果显示:

贷款金额: ¥15,000.48 
利率: 0.8% 
利息: ¥120.00

(3)BigDecimal比较

BigDecimal是通过使用compareTo(BigDecimal)来比较的,具体比较情况如下:

publicstaticvoidmain(String[] args) {
    BigDecimal a = new BigDecimal("1");
    BigDecimal b = new BigDecimal("2");
    BigDecimal c = new BigDecimal("1");
    int result1 = a.compareTo(b);
    int result2 = a.compareTo(c);
    int result3 = b.compareTo(a);
    System.out.println(result1);
    System.out.println(result2);
    System.out.println(result3);
 
}

打印结果是:-1、0、1,即左边比右边数大,返回1,相等返回0,比右边小返回-1。 
注意不能使用equals方法来比较大小。

使用BigDecimal的坏处是性能比double和float差,在处理庞大,复杂的运算时尤为明显,因根据实际需求决定使用哪种类型。

(4)BigDecimal.setScale 处理java小数点

BigDecimal.setScale()方法用于格式化小数点
setScale(1)表示保留一位小数,默认用四舍五入方式 
setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3
setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4
setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4
setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下舍

注释:

1:scale指的是你小数点后的位数。比如123.456则score就是3. 
score()就是BigDecimal类中的方法啊。比如:BigDecimalb = new BigDecimal(“123.456”); 
b.scale(),返回的就是3. 
2:roundingMode是小数的保留模式。它们都是BigDecimal中的常量字段,有很多种。比如BigDecimal.ROUND_HALF_UP表示的就是4舍5入。
3:pubilc BigDecimal divide(BigDecimal divisor, intscale, int roundingMode) 
的意思是说:我用一个BigDecimal对象除以divisor后的结果,并且要求这个结果保留有scale个小数位,roundingMode表示的就是保留模式是什么,是四舍五入啊还是其它的,你可以自己选! 
4:对于一般add、subtract、multiply方法的小数位格式化如下:

BigDecimal mData = new BigDecimal("9.655").setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println("mData=" + mData); 

结果: mData=9.66

 



-------------------------------------------------------------------------------------------------------------------------------------

参考:

Java BigDecimal详解:http://blog.csdn.net/jackiehff/article/details/8582449

 

1 0
原创粉丝点击