Detailed explanation and application of BigDecimal in Java

1. BigDecimal introduction

To borrow a sentence from the book Effactive Java, the main goal of float and double type design is for scientific calculation and engineering calculation. They are mainly used to perform binary floating-point operations, which are carefully designed to provide more accurate and fast approximate calculations in a wide range of values.

Bart, they do not provide completely accurate calculation results, so they should not be used where accurate results are required. However, in business calculations, the results are often required to be accurate. At this time, BigDecimal is of great use.

Java in Java The API class BigDecimal provided in the math package consists of integer non-scale values of arbitrary precision and 32-bit integer scales. If zero or positive, the scale is the number of digits after the decimal point. If it is negative, the non scale value of the number is multiplied by the negative scale power of 10. Therefore, the value represented by BigDecimal is (unscaledValue) × 10 -scale).

BigDecimal is used to perform precise operations on numbers exceeding 16 significant bits. Double, a double precision floating-point variable, can handle 16 bit significant numbers, but in practical applications, it may be necessary to operate and process larger or smaller numbers. Generally, for those numbers that do not need accurate calculation accuracy, we can directly use Float and double, but double Valueof (string) and Float Valueof (string) will lose precision. Therefore, in development, if your business is related to finance and requires accurate calculation results, you must use BigDecimal class to operate!

BigDecimal creates objects. We cannot use traditional arithmetic operators such as +, -, *, / to directly perform mathematical operations on their objects, but must call their corresponding methods. The parameter in the method must also be an object of BigDecimal. Constructors are special methods of classes that are specifically used to create objects, especially objects with parameters.

2. Constructor and method description

2.1. Common constructors

  • BigDecimal(int) creates an object with the integer value specified by the parameter.
  • BigDecimal(double) creates an object with the double value specified by the parameter. [not recommended]
  • BigDecimal(long) creates an object with the long integer value specified by the parameter.
  • BigDecimal(String) creates an object with a value in string specified by the parameter. [recommended]

2.1 common methods

  • Add (BigDecimal) adds the values in the BigDecimal object, and then returns the object.
  • Subtract (BigDecimal) subtracts the value in the BigDecimal object and returns the object.
  • Multiply (BigDecimal) multiplies the values in the BigDecimal object and returns the object.
  • Divide (BigDecimal) divides the values in the BigDecimal object and returns the object.
  • toString() converts the value of the BigDecimal object to a string.
  • doubleValue() returns the value in the BigDecimal object as a double.
  • floatValue() returns the value in the BigDecimal object as a single precision number.
  • longValue() returns the value in the BigDecimal object as a long integer.
  • intValue() returns the value in the BigDecimal object as an integer.

2.3. Explain the constructors that are not recommended

1. Why not use BigDecimal(double) instead of BigDecimal(String)?

	@Test
    public void test1(){
        //BigDecimal(Double)
        BigDecimal doubleStr = new BigDecimal(21.111111);
        //BigDecimal(String)
        BigDecimal intStr = new BigDecimal("21.111111");

        System.out.println(doubleStr);
        System.out.println(intStr);
    }

From the above running results, it can be seen that using double type as construction source will have calculation problems!!!

Description and reason of JDK:

  • The result of the construction method with parameter type double is unpredictable. One might think that the BigDecimal created by writing newBigDecimal(0.1) in Java is exactly equal to 0.1 (non scale value 1, its scale is 1), but it is actually equal to 0.100000000000055512312578217021181583404541015625. This is because 0.1 cannot be accurately expressed as double (or, in this case, as any binary decimal of finite length). In this way, the value passed into the construction method will not be exactly equal to 0.1 (although it is ostensibly equal to this value, it is not 0.1 when participating in the operation within the JDK, resulting in a certain calculation error).
  • On the other hand, the String construction method is completely predictable: writing newBigDecimal("0.1") will create a BigDecimal, which is exactly equal to the expected 0.1. Therefore, in comparison, it is generally recommended to give priority to the String construction method.

2. When double must be used as the construction source of BigDecimal, you can use double ToString (double) is converted to String, and then the String construction method is used, or the static method valueOf() of BigDecimal is used, as follows:

	@Test
    public void test2(){
        BigDecimal doubleStr = new BigDecimal(Double.toString(21.222222));
        BigDecimal intStr = BigDecimal.valueOf(21.222222);

        System.out.println(doubleStr);
        System.out.println(intStr);
    }

3. Application of addition, subtraction, multiplication and division of BigDecimal

3.1 ordinary +, -, */

	@Test
    public void test3(){
        System.out.println(0.2 + 0.1);
        System.out.println(0.3 - 0.1);
        System.out.println(0.2 * 0.1);
        System.out.println(0.3 / 0.1);
    }


The above results are exciting! Then why did the above request appear?

  • Because both float and double are floating-point numbers, the computer only knows binary, and floating-point numbers will lose a certain degree of accuracy.
  • Detailed explanation: decimal values usually do not have exactly the same binary representation; The binary representation of decimal numbers may be imprecise and can only be infinitely close to the exact value.

In real development projects, we can not let this happen, especially in financial and financial projects, because the calculation of the amount involved must be very precise. Think about it. If your Alipay account balance shows 0.99999999999998, what kind of experience is it? Anyway, I can't pick it out!!!

3.2. BigDecimal +, -, */

	 @Test
    public void test4(){
        BigDecimal num1 = new BigDecimal("0.3");
        BigDecimal num2 = new BigDecimal("0.1");

        System.out.println("Add:" + num1.add(num2));
        System.out.println("Minus:" + num1.subtract(num2));
        System.out.println("Multiply:" + num1.multiply(num2));
        System.out.println("Except:" + num1.divide(num2));
    }

3.3. Pay special attention to the situation that BigDecimal division cannot be divided completely (rounding is required)

If the following exception occurs:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

reason:

If the result is not divisible and has a remainder during division, it will be reported to Java Lang. arithmeticexception:, here we want to avoid this error. For the calculation of possible decimal during division operation, in the divide method, two more parameters must be passed after the divisor: divide (divisor, keep the decimal digits after the decimal point, rounding mode).

Rounding mode:

  • ROUND_UP: rounding away from 0
  • ROUND_DOWN: round to zero
  • ROUND_CEILING: round to positive infinity
  • ROUND_FLOOR: round to negative infinity
  • ROUND_HALF_UP: round to the nearest side. If the two sides are equal, round up to 1.55 and keep one decimal place. The result is 1.6, which is what we often call "rounding"
  • ROUND_HALF_DOWN: round to the nearest side. If the two sides are equal, round down. For example, 1.55, keep one decimal place, and the result is 1.5
  • ROUND_HALF_EVEN: round to the nearest side. If both sides are equal, if the reserved digits are odd, use ROUND_HALF_UP, if even, use ROUND_HALF_DOWN
  • ROUND_UNNECESSARY: the calculation result is accurate and does not need rounding mode

Case 1:

	@Test
    public void test5(){
        double aDouble = 1.233;
        float aFloat = 3.3f;

        BigDecimal num3 = new BigDecimal(Double.toString(aDouble));
        BigDecimal num4 = new BigDecimal(Float.toString(aFloat));

        System.out.println("Multiplication:" + num3.multiply(num4));

        //System.out.println("division:" + num3.divide(num4))// Will report an error
        System.out.println("Division, rounded to two decimal places:" + num3.divide(num4,2,BigDecimal.ROUND_HALF_UP));
    }


Case 2:

	@Test
    public void test6(){

        BigDecimal intStr = BigDecimal.valueOf(10);
        BigDecimal doubleStr = new BigDecimal(Double.toString(3));

		//The following represents the value of rounding mode
        System.out.println("ROUND_UP: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_UP));//0
        System.out.println("ROUND_DOWN: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_DOWN));//1
        System.out.println("ROUND_CEILING: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_CEILING)); //2
        System.out.println("ROUND_FLOOR: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_FLOOR));//3
        System.out.println("ROUND_HALF_UP: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_UP));//4
        System.out.println("ROUND_HALF_DOWN: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_DOWN));//5
        System.out.println("ROUND_HALF_EVEN: " + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_EVEN));//6
    }

Operation results:

Note: this divide method has two overloaded methods. One is to pass two parameters and the other is to pass three parameters.

//Of two parameters (only the rounding mode value needs to be passed in, but the number of digits is reserved. Press the default number of digits of the rounding mode)
public BigDecimal divide(BigDecimal divisor, int roundingMode) {
        return this.divide(divisor, scale, roundingMode);
}


//Three parameter
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
        if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY)
            throw new IllegalArgumentException("Invalid rounding mode");
        if (this.intCompact != INFLATED) {
            if ((divisor.intCompact != INFLATED)) {
                return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
            } else {
                return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
            }
        } else {
            if ((divisor.intCompact != INFLATED)) {
                return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
            } else {
                return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
            }
        }
    }

BigDecimal needs to be truncated and rounded. You can use the setScale method, for example:

	@Test
    public void test7(){
        BigDecimal num = new BigDecimal("10.254");
        System.out.println(num.setScale(2,BigDecimal.ROUND_HALF_UP ));//Round to two decimal places
    }

Keywords: BigDecimal

Added by nikefido on Fri, 10 Dec 2021 14:03:48 +0200