Java sample code for high precision calculations using BigDecimal

  • 2020-10-23 20:57:32
  • OfStack

Let's start with the following code example:


System.out.println(0.05 + 0.01);
System.out.println(0.05 - 0.03);
System.out.println(1.025 * 100);
System.out.println(305.1 / 1000);

The output result is:

0.060000000000000005
0.020000000000000004
102.49999999999999
0.30510000000000004

The Java language supports two basic floating point types: float and double, and their corresponding wrapper classes Float and Double. Both are based on the IEEE 754 standard, which defines the base 2 standard for 32-bit floating-point and 64-bit double-precision floating-point 2-decimal Numbers.

IEEE 754 USES scientific notation to represent floating point Numbers as decimals with base 2. IEEE floating-point Numbers use 1 bit for the symbol of a number, 8 bits for the exponent, 23 bits for the mantissa, or decimal part, the exponent as a signed integer can be positive or negative, and the decimal part as a base 2 decimal

Do not use floating point values to represent exact values

Some non-integral values (such as decimals of dollars and cents) need to be very precise. Floating point Numbers are not exact values, so using them results in rounding errors. Therefore, it is not a good idea to use floating point Numbers to try to represent exact quantities such as monetary quantities. Using floating-point Numbers for dollar and cent calculations can have disastrous results. Floating point Numbers are best used to represent values such as measurements, which are less precise starting at 1.

Using BigDecimal

Since JDK 1.3, Java developers have had another numerical representation for non-integers: BigDecimal. BigDecimal is a standard class that requires no special support in the compiler to represent decimal Numbers of arbitrary precision and to compute them.

Methods for adding, subtracting, multiplying, and dividing provide arithmetic operations for BigDecimal values. Since the BigDecimal object is immutable, each of these methods produces a new BigDecimal object. Therefore, BigDecimal is not suitable for a lot of math because of the overhead of creating objects, but it is designed to represent decimals accurately. If you are looking for an exact representation of a value such as the amount of money, BigDecimal is well suited for the task.

Tectonic BigDecimal number

For BigDecimal, several constructors are available. One of these constructors takes double floating-point Numbers as input, one takes integers and conversion factors, and one takes the String representation of decimal Numbers as input. Be careful with the BigDecimal(double) constructor, because if you don't know it, you can generate rounding errors during the calculation. Use integer or String based constructors.


public class Test {
  public static void main(String[] args) {
    //  Constructed as a double - precision floating - point number 
    BigDecimal bd1 = new BigDecimal(0.5);
    BigDecimal bd2 = new BigDecimal(0.1);
    System.out.println(bd1.add(bd2));

    //  In order to String Type construction 
    BigDecimal bd3 = new BigDecimal("0.5");
    BigDecimal bd4 = new BigDecimal("0.1");
    System.out.println(bd3.add(bd4));
  }
}

The output result is:

0.6000000000000000055511151231257827021181583404541015625
0.6

The above code is as follows


BigDecimal(double val)
BigDecimal(String val)

The BigDecimal number is constructed in different ways, and the output result is different.

Going back to the original example, the utility classes are provided to perform precise floating-point arithmetic, including addition, subtraction, multiplication, and division, and rounding 4 into 5.


import java.math.BigDecimal;

public class ArithUtil {
  private static final int DEF_DIV_SCALE = 6; //  Default division precision 

  /**
   *  Provides accurate addition operations. 
   *
   * @param v1  augend 
   * @param v2  addend 
   * @return  The sum of two parameters 
   */
  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();
  }

  /**
   *  Provides accurate subtraction operations. 
   *
   * @param v1  minuend 
   * @param v2  reduction 
   * @return  The difference between the two parameters 
   */
  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();
  }

  /**
   *  Provides accurate multiplication. 
   *
   * @param v1  multiplicand 
   * @param v2  The multiplier 
   * @return  The product of two parameters 
   */
  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();
  }

  /**
   *  Provides (relatively) accurate division operation, when the case of endless division, accurate to   After the decimal point 10 Bits, later Numbers 4 Give up 5 Into the. 
   *
   * @param v1  dividend 
   * @param v2  divisor 
   * @return  The quotient of two parameters 
   */
  public static double div(double v1, double v2) {
    return div(v1, v2, DEF_DIV_SCALE);
  }

  /**
   *  Provides (relatively) accurate division operations. When an inexhaustibility occurs, by scale Parameter refers to   Fixed precision, subsequent Numbers 4 Give up 5 Into the. 
   *
   * @param v1  dividend 
   * @param v2  divisor 
   * @param scale  To be accurate to a few places below the decimal point. 
   * @return  The quotient of two parameters 
   */
  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();
  }

  /**
   *  Provide the exact decimal place 4 Give up 5 In the process. 
   *
   * @param v  Need to be 4 Give up 5 Into the digital 
   * @param scale  Keep a few places behind the decimal point 
   * @return 4 Give up 5 Results after entry 
   */
  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();
  }
}

Conclusion:

Using floating point and decimal Numbers in the Java program is rife with pitfalls. Floating-point and decimal Numbers are not as "well behaved" as integer 1, and cannot be assumed to produce integers or exact results, although they "should" do so. It is best to keep floating point operations for calculations that are inherently imprecise, such as measurements. If you need to represent a fixed point number (for example, a few dollars and a few cents), use BigDecimal.


Related articles: