详解BigDecimal及其加减乘除运算

 

目录

 

一、BigDecimal概述

二、构造函数详解

1、BigDecimal(int val)

2、BigDecimal(int val, MathContext mc)

     MathContext

3、BigDecimal(long val)

4、BigDecimal(long val, MathContext mc)

5、BigDecimal(BigInteger val)

6、BigDecimal(BigInteger unscaledVal, int scale)

7、BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)

8、BigDecimal(BigInteger val, MathContext mc)

9、BigDecimal(double val)

10、BigDecimal(double val, MathContext mc)

11、BigDecimal(char[] in)

12、BigDecimal(char[] in, int offset, int len)

13、BigDecimal(char[] in, int offset, int len, MathContext mc)

14、BigDecimal(char[] in, MathContext mc)

15、BigDecimal(String val)

16、BigDecimal(String val, MathContext mc)

三、BigDecimal的加、减、乘、除、绝对值

1、加法

2、减法

3、乘法

4、除法

     RoundingMode

5、绝对值

 


一、BigDecimal概述

       浮点数值不适用于无法接受舍入误差的计算当中,比如金融计算。

       例如,System.out.println(2.0-1.1)打印的结果为0.8999999999999999,而不是人们想象当中的0.9,这是为什么呢?因为System.out.println()中的数字默认是double类型的,而浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示小数0.1,因此产生了这种舍入误差。

       就像在十进制当中,无法精确地表示分数1/3一样。

       既然浮点数都不能精确的表示出来,那么通过浮点数进行的加减乘除运算自然都不是精确的值。

       如果在数值计算中需要精确的结果,不允许有这种舍入误差,那么就需要使用BigDecima类。

       BigDecima是java.math包中的一个类,这个类可以处理任意长度数字序列的数值,实现了任意精度的浮点数运算。类似BigDecima,还有一个Biglnteger,实现了任意精度的整数运算。

 

       BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。

二、构造函数详解

1、BigDecimal(int val)

       将 int转换成 BigDecimal对象。例如:

       BigDecimal a = new BigDecimal(8);

       这是通过构造函数的方式,将一个整型值转换成BigDecimal 对象,还可以通过静态的valueOf方法将普通的数值转换成BigDecimal。例如:

       BigDecimal b = BigDecimal.valueOf(100);将long型数值转换成(小数位数为零的)BigDecimal。

       BigDecimal c = BigDecimal.valueOf(3.14);将double型数值转换成BigDecimal。

2、BigDecimal(int val, MathContext mc)

       将 int转换成 BigDecimal ,根据上下文设置进行舍入。

       这里需要先讲述一下MathContext

     MathContext

       MathContext是一个不可变对象,用于封装上下文设置,这些设置描述数字运算符的某些规则。它有三个构造函数:

(1)MathContext(int setPrecision)

       构造具有指定精度的MathContext对象,默认的是HALF_UP舍入模式(关于舍入模式,本文后面会进行详细讲解)。

(2)MathContext(int setPrecision, RoundingMode setRoundingMode)

       使用指定的精度和舍入模式构造新的MathContext对象。

(3)MathContext(String val)

       使用字符串构造一个MathContext对象。

       使用示例如下,

MathContext mathContext = new MathContext(4);
BigDecimal d = new BigDecimal(12345, mathContext);
System.out.println(d);

       打印的结果为:1.235E+4

       分析上述代码。第一行代码创建了一个MathContext 对象,指定的精度为4,也就是4位有效数字。第二行代码利用整型的“12345”和mathContext创建了一个BigDecimal对象,将12345保留4位有效数字转换成BigDecimal。12345保留四位有效数字的结果也就是1.235E+4(1.235乘于10的四次方)。

3、BigDecimal(long val)

       将 long转换成 BigDecimal 。

4、BigDecimal(long val, MathContext mc)

       将 long转换为 BigDecimal ,根据上下文设置进行舍入。

5、BigDecimal(BigInteger val)

       将 BigInteger转换成 BigDecimal 。

6、BigDecimal(BigInteger unscaledVal, int scale)

       将BigInteger的 BigInteger值和 int等级转换为 BigDecimal 。

7、BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)

       将 BigInteger未缩放值,根据scale和mc ,转换为 BigDecimal 。

8、BigDecimal(BigInteger val, MathContext mc)

       根据上下文设置将 BigInteger转换为 BigDecimal,根据上下文设置进行舍入。

9、BigDecimal(double val)

       将 double转换为 BigDecimal ,这是 double的二进制浮点值的精确十进制表示。

10、BigDecimal(double val, MathContext mc)

       将 double转换为 BigDecimal ,根据上下文设置进行舍入。

11、BigDecimal(char[] in)

       将字符数组转换成 BigDecimal 。

12、BigDecimal(char[] in, int offset, int len)

       将字符数组转换成 BigDecimal,同时允许一个子阵列被指定。

13、BigDecimal(char[] in, int offset, int len, MathContext mc)

       将字符数组转换成 BigDecimal,同时允许指定一个子阵列和用根据上下文设置进行舍入。

14、BigDecimal(char[] in, MathContext mc)

      将字符数组转换成 BigDecimal ,根据上下文设置进行舍入。

15、BigDecimal(String val)

       常用!!!将字符串表示的数字转换为 BigDecimal 。

16、BigDecimal(String val, MathContext mc)

       常用!!!将字符串表示的数字转换为 BigDecimal ,根据上下文设置进行舍入。

       可以看到,BigDecimal的构造函数很多,根据自己的需要,大家选择合适的构造函数创建对应的BigDecimal对象就行了。但是有一点需要注意,就是以数值型参数创建BigDecimal对象的时候,会产生精度问题。

      例如,

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal("0.1"));

       打印结果为:

 

0.1000000000000000055511151231257827021181583404541015625
0.1

       可以看到,通过double类型的0.1创建BigDecimal对象的时候,实际打印的值并不是精确的0.1,通过String类型创建的BigDecimal对象,实际打印的值是0.1。这是为什么呢?

       因为参数类型为double的构造方法的结果有一定的不可预知性。不熟悉BigDecimal的人可能会认为在Java中通过newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1,但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。因为0.1无法用二进制精确地表示出来,本来开头亦对此进行了解释。这样,传入到构造方法的值并不是我们看到的0.1,实际上传的是0.1000000000000000055511151231257827021181583404541015625,因此产生了上述的打印结果。

       String 构造方法是完全可预知的:通过 newBigDecimal(“0.1”) 将创建一个 BigDecimal对象,它正好等于预期的 0.1。因此, 通常建议优先使用String构造方法。

三、BigDecimal的加、减、乘、除、绝对值

1、加法

       加法对应的方法有两个:

(1)public BigDecimal add(BigDecimal augend);

       方法返回的是一个BigDecimal对象,值为当前对象的值加上被加数对象augend的值(this+augend)。保留的小数位数是max(this.scale(), augend.scale()).

(2) public BigDecimal add(BigDecimal augend,MathContext mc);

       方法返回的是一个BigDecimal对象,值为当前对象的值加上被加数对象augend的值(this+augend),并根据上下文设置进行舍入。(关于上下文MathContext,前文已解释过)

       加法示例如下,

    @Test
    public void testAdd() {
        //用double类型初始化BigDecimal对象
        BigDecimal numA = new BigDecimal(0.05);
        BigDecimal numB = new BigDecimal(0.06);
        System.out.println("numA + numB = " + numA.add(numB));
        //用double类型和int类型初始化BigDecimal对象。(作加法运算时得到的只是一个近似值)
        BigDecimal numC = new BigDecimal(3.05);
        BigDecimal numD = new BigDecimal(100);
        System.out.println("numC + numD = " + numC.add(numD));
        //用字符串类型初始化BigDecimal对象。(作加法运算时得到的是精确值)
        BigDecimal strA = new BigDecimal("3.05");
        BigDecimal strB = new BigDecimal("100");
        System.out.println("strA + strB = " + strA.add(strB));
    }

      打印的结果如下:

numA + numB = 0.11000000000000000055511151231257827021181583404541015625
numC + numD = 103.04999999999999982236431605997495353221893310546875
strA + strB = 103.05

       可以看到,使用double类型初始化BigDecimal 对象,进行加法运算时就出现了精度问题。正如前文所说,并不是所有的浮点数都能够在二进制系统中被精确的表示,自然而然的在进行加减乘除运算时就会出错。

2、减法

       对应的方法有两个:
(1)public BigDecimal subtract(BigDecimal subtrahend);

       方法返回的是一个BigDecimal对象,值为当前对象的值减去被减数对象subtrahend的值(this-subtrahend)。保留的小数位数是max(this.scale(), subtrahend.scale()).。   

(2)public BigDecimal subtract(BigDecimal subtrahend,MathContext mc);

       方法返回的是一个BigDecimal对象,值为当前对象的值减去被减数对象subtrahend的值(this-subtrahend),并根据上下文设置进行舍入。

       减法示例如下,

    @Test
    public void testSubtract() {
        //用double类型初始化BigDecimal对象
        BigDecimal numA = new BigDecimal(0.05);
        BigDecimal numB = new BigDecimal(0.06);
        System.out.println("numA + numB = " + numA.subtract(numB));
        //用double类型和int类型初始化BigDecimal对象。(作减法运算时得到的只是一个近似值)
        BigDecimal numC = new BigDecimal(100);
        BigDecimal numD = new BigDecimal(0.05);
        System.out.println("numC + numD = " + numC.subtract(numD));
        //用字符串类型初始化BigDecimal对象。(作减法运算时得到的是精确值)
        BigDecimal strA = new BigDecimal("100");
        BigDecimal strB = new BigDecimal("0.05");
        System.out.println("strA + strB = " + strA.subtract(strB));
    }

       打印结果如下:

numA + numB = -0.00999999999999999500399638918679556809365749359130859375
numC + numD = 99.94999999999999999722444243843710864894092082977294921875
strA + strB = 99.95

3、乘法

       对应的方法有:

(1)public BigDecimal multiply(BigDecimal multiplicand);

       方法返回的是一个BigDecimal对象,值为当前对象的值乘于被乘数对象multiplicand的值(this*multiplicand)。保留的小数位数是(this.scale() + multiplicand.scale()).。   

(2)public BigDecimal multiply(BigDecimal multiplicand,MathContext mc);

      方法返回的是一个BigDecimal对象,值为当前对象的值乘于被乘数对象subtrahend的值(this*multiplicand),并根据上下文设置进行舍入。

      乘法示例如下,

    @Test
    public void testMultiply() {
        //用double类型初始化BigDecimal对象
        BigDecimal numA = new BigDecimal(0.05);
        BigDecimal numB = new BigDecimal(0.06);
        System.out.println("numA + numB = " + numA.multiply(numB));
        //用double类型和int类型初始化BigDecimal对象。(作乘法运算时得到的只是一个近似值)
        BigDecimal numC = new BigDecimal(100);
        BigDecimal numD = new BigDecimal(0.05);
        System.out.println("numC + numD = " + numC.multiply(numD));
        //用字符串类型初始化BigDecimal对象。(作乘法运算时得到的是精确值)
        BigDecimal strA = new BigDecimal("100");
        BigDecimal strB = new BigDecimal("0.05");
        System.out.println("strA + strB = " + strA.multiply(strB));
    }

       打印结果如下,

numA + numB = 0.00300000000000000005551115123125782085820576136538628584587058372823258067807472571075777523219585418701171875
numC + numD = 5.00000000000000027755575615628913510590791702270507812500
strA + strB = 5.00

 

4、除法

       对应的方法主要有:

(1) public BigDecimal divide(BigDecimal divisor);

       返回一个BigDecimal,其值为(this/divisor),首选小数位数为(this.scale()-divisor.scale());如果无法表示精确的商(因为它具有非终止的十进制扩展),则会引发算术异常。

(2) public BigDecimal divide(BigDecimal divisor,MathContext mc);

       返回值为(this/divisor)的BigDecimal,并根据上下文设置进行舍入。

 (3) public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode);

       返回一个BigDecimal,其值为(this/divisor),其小数位数与指定的相同。如果必须执行舍入才能生成具有指定比例的结果,则应该用指定的舍入模式。

(4) public BigDecimal divide(BigDecimal divisor,int scale,RoundingMode roundingMode);

       返回一个BigDecimal,其值为(this/divisor),其小数位数与指定的相同(由第二个参数scale指定)。如果必须执行舍入才能生成具有指定比例的结果,则应该用指定的舍入模式(由第三个参数roundingMode指定)。

       (3)(4)两个方法其实是一样的,区别只在于第三个参数的指定方式。第一个方法是int roundingMode,即传入的是一个整型数字,而第二个方法是RoundingMode roundingMode,即传入的是一个RoundingMode对象。

       这里需要对RoundingMode作一下介绍。

     RoundingMode

       RoundingMode是一个枚举类,内部有8个枚举常量,分别是:

       UP:远离0的舍入模式,在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。放到一维坐标轴上就很容易理解,就是以0为分隔点,0右侧部分一直向右舍入,0左侧部分一直向左侧舍入。例如0.333保留两位小数,采用UP舍入模式的结果为0.34,-0.333保留两位小数,采用UP舍入模式的结果为-0.34。

详解BigDecimal及其加减乘除运算_第1张图片

       DOWN:和UP相反,是接近零的舍入模式。理解了UP舍入模式,就很容易理解DOWN舍入模式。例如0.333保留两位小数,采用DOWN舍入模式的结果为0.33,-0.333保留两位小数,采用DOWN舍入模式的结果为-0.33。可以发现,在采用DOWN模式进行舍入的时候,直接把需要舍弃的位数丢掉就行了。

详解BigDecimal及其加减乘除运算_第2张图片

       CEILING:CEILING英文是天花板的意思,可以理解为向”大“舍入。如果 BigDecimal 为正,则舍入行为与 UP 相同,如果为负,则舍入行为与 DOWN 相同。例如0.333保留两位小数,采用CEILING舍入模式的结果为0.34(0.34 >0.333),-0.333保留两位小数,采用CEILING舍入模式的结果为-0.33(-0.33>0.333 )。

详解BigDecimal及其加减乘除运算_第3张图片

       FLOOR:与CEILING相反,FLOOR有地板的意思,可以理解为向”小“舍入。如果 BigDecimal 为正,则舍入行为与 DOWN 相同,如果为负,则舍入行为与 UP 相同。例如0.333保留两位小数,采用FLOOR舍入模式的结果为0.33,-0.333保留两位小数,采用FLOOR舍入模式的结果为-0.34。

详解BigDecimal及其加减乘除运算_第4张图片

       HALF_UP:向”最接近的“数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 UP 相同,否则舍入行为与 DOWN 相同。其实就是四舍五入。

       HALF_DOWN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与UP 相同,否则舍入行为与 DOWN 相同。与四舍五入的思路是一样的,只不过这里是”五舍六入“。如果扣字面意思的话,也很好理解,HALF_UP,half是一半的意思,也就是5,UP是向上的意思,就可以理解为5向上入,即四舍五入。同理,HALF_DOWN,5向下舍,即五舍六入。

       HALF_EVEN:向“最接近的”数字舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 UP 相同,如果为偶数,则舍入行为与 DOWN 相同。如0.135,保留两位小数,舍弃5,因为3是奇数,所以与UP相同,结果为0.14;0.125,保留两位小数,舍弃5,因为2是偶数,所以与DOWN相同,结果为0.12.

       UNNECESSARY:以断言请求的操作具有精确的结果,因此不需要舍入。如果有舍入,会报java.lang.ArithmeticException异常。

       关于这几个舍入模式的结果,亦可参照下面的表进行快速学习。

保留整数,不同舍入模式下的计算结果
原数 UP DOWN CEILING FLOOR HALF_UP HALF_DOWN HALF_EVEN UNNECESSARY
5.5 6 5 6 5 6 5 6 throw ArithmeticException
2.5 3 2 3 2 3 2 2 throw ArithmeticException
1.6 2 1 2 1 2 2 2 throw ArithmeticException
1.1 2 1 2 1 1 1 1 throw ArithmeticException
1.0 1 1 1 1 1 1 1 1
-1.0 -1 -1 -1 -1 -1 -1 -1 -1
-1.1 -2 -1 -1 -2 -1 -1 -1 throw ArithmeticException
-1.6 -2 -1 -1 -2 -2 -2 -2 throw ArithmeticException
-2.5 -3 -2 -2 -3 -3 -2 -2 throw ArithmeticException
-5.5 -6 -5 -5 -6 -6 -5 -6 throw ArithmeticException
                 

       代码示例如下,

@Test
public void testDivide() {
        BigDecimal numA = new BigDecimal("1");
        BigDecimal numB = new BigDecimal("-1");
        BigDecimal numC = new BigDecimal("3");
        // 保留两位小数,舍入模式为UP
        System.out.println("1/3保留两位小数(UP) = " + numA.divide(numC, 2, RoundingMode.UP));
        System.out.println("-1/3保留两位小数(UP) = " + numB.divide(numC, 2, RoundingMode.UP));
        // 保留两位小数,舍入模式为DOWN
        System.out.println("1/3保留两位小数(DOWN) = " + numA.divide(numC, 2, RoundingMode.DOWN));
        System.out.println("-1/3保留两位小数(DOWN) = " + numB.divide(numC, 2, RoundingMode.DOWN));
        // 保留两位小数,舍入模式为CEILING
        System.out.println("1/3保留两位小数(CEILING) = " + numA.divide(numC, 2, RoundingMode.CEILING));
        System.out.println("-1/3保留两位小数(CEILING) = " + numB.divide(numC, 2, RoundingMode.CEILING));
        // 保留两位小数,舍入模式为FLOOR
        System.out.println("1/3保留两位小数(FLOOR) = " + numA.divide(numC, 2, RoundingMode.FLOOR));
        System.out.println("-1/3保留两位小数(FLOOR) = " + numB.divide(numC, 2, RoundingMode.FLOOR));

        BigDecimal numD = new BigDecimal("1");
        BigDecimal numE = new BigDecimal("-1");
        BigDecimal numF = new BigDecimal("8");
        // 保留两位小数,舍入模式为HALF_UP
        System.out.println("1/8(=0.125)保留两位小数(HALF_UP) = " + numD.divide(numF, 2, RoundingMode.HALF_UP));
        System.out.println("-1/8(=0.125)保留两位小数(HALF_UP) = " + numE.divide(numF, 2, RoundingMode.HALF_UP));
        // 保留两位小数,舍入模式为HALF_DOWN
        System.out.println("1/8(=0.125)保留两位小数(HALF_DOWN) = " + numD.divide(numF, 2, RoundingMode.HALF_DOWN));
        System.out.println("-1/8(=0.125)保留两位小数(HALF_DOWN) = " + numE.divide(numF, 2, RoundingMode.HALF_DOWN));

        // 保留两位小数,舍入模式为HALF_EVEN
        System.out.println("0.54/4(=0.135)保留两位小数(HALF_EVEN) = " + new BigDecimal("0.54").divide(new BigDecimal("4"), 2, RoundingMode.HALF_EVEN));
        System.out.println("1/8(=0.125)保留两位小数(HALF_EVEN) = " + numE.divide(numF, 2, RoundingMode.HALF_EVEN));

        //UNNECESSARY,会报异常
        System.out.println("1/8(=0.125) = " + numE.divide(numF,  RoundingMode.UNNECESSARY));
}

打印结果:

1/3保留两位小数(UP) = 0.34
-1/3保留两位小数(UP) = -0.34
1/3保留两位小数(DOWN) = 0.33
-1/3保留两位小数(DOWN) = -0.33
1/3保留两位小数(CEILING) = 0.34
-1/3保留两位小数(CEILING) = -0.33
1/3保留两位小数(FLOOR) = 0.33
-1/3保留两位小数(FLOOR) = -0.34
1/8(=0.125)保留两位小数(HALF_UP) = 0.13
-1/8(=0.125)保留两位小数(HALF_UP) = -0.13
1/8(=0.125)保留两位小数(HALF_DOWN) = 0.12
-1/8(=0.125)保留两位小数(HALF_DOWN) = -0.12
0.54/4(=0.135)保留两位小数(HALF_EVEN) = 0.14
1/8(=0.125)保留两位小数(HALF_EVEN) = -0.12

java.lang.ArithmeticException: Rounding necessary

5、绝对值

   public BigDecimal abs();

       返回一个BigDecimal,其值为该BigDecimal的绝对值,其小数位数为this.scale()。

   public BigDecimal abs(MathContext mc);

      返回一个BigDecimal,其值是此BigDecimal的绝对值,并根据上下文设置进行舍入。

      代码示例,

    @Test
    public void testAbs() {
        BigDecimal a = new BigDecimal("1.234");
        BigDecimal b = new BigDecimal("-1.234");
        System.out.println("1.234的绝对值:" + a.abs());
        System.out.println("-1.234的绝对值:" + b.abs());
        System.out.println("-1.234的绝对值,保留两位有效数字:" + b.abs(new MathContext(2)));
    }

       打印结果为:

1.234的绝对值:1.234
-1.234的绝对值:1.234
-1.234的绝对值,保留两位有效数字:1.2

 

 

 

你可能感兴趣的:(Java,SE)