BigDecimal 的介绍及使用

一、BigDecimal 的介绍

    BigDecimal是Java在java.math包中提供的API类,用来对超过16位有效位的数进行精确的运算。虽然double类型的变量也可以处理16位有效数,但是在实际应用中,也会出现对更大或者更小的数进行运算和处理的。float和double只能用来做科学计算或者是工程计算。
    BigDecimal创建的是对象,不能使用传统的+、-、*、/等算数运算符来对其进行数学运算,而是应该采用该类下面的对应的方法,方法中的参数也必须是BigDecimal对象。
    一般情况下,对于那些不需要准确精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(Stirng)和Float.valueOf(String)会丢失精度,所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

二、BigDecimal的构造方法

    1、常用构造函数
        BigDecimal(int):创建一个具有参数所指定整数值的对象;
        BigDecimal(double):创建一个具有参数所指定双精度值的对象;
        BigDecimal(long):创建一个具有参数所指定的长整数值的对象;
        BigDecimal(String):创建一个具有参数所指定以字符串表示的数值的对象。
    2、使用注意:
        当使用double类型创建BigDecimal对象的时候,可能会出现精度误差,不建议使用。如果想要创建Double类型的,建议使用new BigDecimal(Double.toString(bigDecimal))的形式。
        代码详解:

public static void main(String[] args) {
    double dou = 0.1;
    BigDecimal big1 = new BigDecimal(dou);
    System.out.println("big1 = " + big1);
    BigDecimal big2 = new BigDecimal(Double.toString(dou));
    System.out.println("big2 = " + big2);
}
结果:
big1 = 0.1000000000000000055511151231257827021181583404541015625
big2 = 0.1

图片粘不上来,就将结果直接粘到上面了,有兴趣的同学自己去IDE里运行一下,看结果是否一致。
    原因:
        参数类型为double的构造方法的结果具有一定的不可预知性。就像上面的程序表示的那样,虽然new BigDecimal(0.1)所创建的Bigdecimal是0.1,但是实际上得到的值确是不同的,因为0.1无法准确地表示为double,所以传入构造方法的值也就出现了误差。
        而String 类型的构造方法是完全可以预知到的,参数给的是“0.1”,得到的结果就是0.1.所以使用该类型的构造方法更能符合自己预期中的值。
        当double必须用作BigDecimal的源时,可以使用BigDecimal.valueOf(double)的方法来进行,因为该方法的底层,也是将double值转换为String类型,在进行运算的。

public static BigDecimal valueOf(double val) {
     // Reminder: a zero double returns '0.0', so we cannot fastpath
     // 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));
}
// ps:不知道今天怎么了,图片都贴不上来,只能将源码粘上来了。
// 这里可以看到,valueOf的底层还是 BigDecimal(String)实现的。

    3、为什么使用double类型会出现精度误差
        其实我们平时使用的语言大多数都有精度不准确的情况,不是Java独有的。
        出现这种情况的原因就要从我们计算机的数据表现形式说起了,计算机底层所有数据的表现形式都为二进制形式,一般情况下,二进制转为十进制所使用的方法是按权相加法,十进制转二进制是除2取余的逆序排列法。

二进制到十进制:
10010:   0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 0 * 2^3 + 1 * 2^4 = 18   

十进制到二进制:
18 / 2 =9。 余0
9 / 2 = 4。  余1
4 / 2 = 2。  余0
2 / 2 = 1。  余0
1 / 2 = 0。  余1
得出的结果就是10010
进制数之间的转换相信大家已经很熟了,如果忘记的,看这个算法大概都能回想起来了吧,这里就不做详细解释了

    上面是整数转换的方法,在小数部分,使用乘2取整数位,顺序排列。二进制小数到十进制小数还是按权相加法。

二进制到十进制:
10.01 = 1 * 2^-2 + 0 * 2^-1 + 0 * 2^0 + 1 * 2^1 = 2.25

十进制到二进制(小数部分):
0.25
0.25 * 2 = 0.5 .... 0
0.5 * 2 = 1 .... 1
结果等于0.01

那是进制之间的转换问题,那为什么浮点运算会有不准确的情况呢?下面进入正题,以2.1举例

// 整数部分 
 2 / 2 = 1 .... 0
 1 / 2 = 0 .... 1
// 小数部门
0.1 * 2 = 0.2 .... 0
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0 
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0 
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
............

    这就是无限循环,结果为 10.0001100110011........ , 我们的计算机在存储小数时肯定是有长度限制的,所以会进行截取部分小数进行存储,从而导致计算机存储的数值只能是个大概的值,而不是精确的值。从这里看出来我们的计算机根本就无法使用二进制来精确的表示 2.1 这个十进制数字的值,连表示都无法精确表示出来,计算肯定是会出现问题的。

三、BigDecimal常用方法详解

  1、常用方法:
    add(BigDecimal):BigDecimal对象中的值相加,返回BigDecimal对象
    subtract(BigDecimal):BigDecimal对象中的值相减,返回BigDecimal对象
    multiply(BigDecimal):BigDecimal对象中的值相乘,返回BigDecimal对象
    divide(BigDecimal):BigDecimal对象中的值相除,返回BigDecimal对象
    abs():将BigDecimal对象中的值转换成绝对值
    doubleValue():将BigDecimal对象中的值转换成双精度数
    floatValue():将BigDecimal对象中的值转换成单精度数
    longValue():将BigDecimal对象中的值转换成长整数
    intValue():将BigDecimal对象中的值转换成整数
    toString():将BigDecimal对象中的值转换成字符串
  2、大小比较
    java中对BigDecimal比较大小一般用的是BigDemical的compareTo方法.
        举例:new BigDemica("0.1").compareTo(new BigDemical(0.2))
    关于这些方法的使用,大家可以去IDE中执行一下,非常好上手,这里不做详细展示了。

四、BigDecimal的八种舍入模式

    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则向下舍
    setScaler(1,BigDecimal.ROUND_CEILING)接近正无穷大的舍入
    setScaler(1,BigDecimal.ROUND_FLOOR)接近负无穷大的舍入,数字>0和ROUND_UP作用一样,数字<0和ROUND_DOWN作用一样
    setScaler(1,BigDecimal.ROUND_HALF_EVEN)向最接近的数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。

    1、ROUND_UP,向远离0的方向舍入,始终对非零舍弃部位前面的数字+1,该方式就是只增不减。
    2、ROUND_DOWN,向0方向舍入,在丢弃某部分之前,始终不增加数据(即,截断),该方式是只减不加。
    3、ROUND_CEILING,向正无穷方向舍入,如果数值为正,舍入方式与ROUND_UP一致,如果为负,舍入方式与ROUND_DOWN一致,该模式始终不会减少计算数值。
    4、ROUND_FLOOR,向负无穷方向舍入,如果数值为正,舍入行为与 ROUND_DOWN 相同;如果为负,则舍入行为与 ROUND_UP 相同。该模式始终不会增加计算数值。
    5、ROUND_HALF_UP,向“最接近的”数字摄入,也就是四舍五入。
    6、ROUND_HALF_DOWN,向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式,也就是五舍六入。
    7、ROUND_HALF_EVEN,向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。(向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用四舍五入,如果是偶数,使用五舍六入),此模式也被称为“银行家舍入法”,主要在美国使用。
        eg.   1.15->1.2,      1.25->1.2
    8、ROUND_UNNECESSARY,计算结果是精确的,不需要舍入模式。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

五、总结

    BigDecimal的性能比double和float都差,在处理庞大、复杂的运算时尤为明显,所以一般精度的计算没必要使用BigDecimal,在需要精确的小数计算时再使用。
    创建BigDecimal对象时,尽量使用参数类型为String的构造函数。
    BigDecimal都是不可变的,在进行每一次四则运算时,都会产生一个新对象,所以在做加减乘除运算时要保存一下操作后的值哦。

你可能感兴趣的:(BigDecimal 的介绍及使用)