Interviewer: is BigDecimal sure not to lose accuracy?

We have basically formed common sense that BigDecimal should be used where money is needed instead of others, and we all know that floating-point variables will lose accuracy when calculating.

So, do you know that BigDecimal will also lose accuracy? Is there anything worth exploring behind the use of BigDecimal? I know, but I also tell you today.

The following code:

System.out.println(0.05 + 0.01);  
System.out.println(1.0 - 0.42);  
System.out.println(4.015 * 100);  
System.out.println(123.3 / 100);

Output:

0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

It can be seen that the problem of losing precision will occur when floating-point arithmetic is performed in Java. Then, if we calculate the commodity price, there will be problems.

It is likely that we have 0.06 yuan in our hands, but we can't buy a 0.05 yuan and a 0.01 yuan commodity.

As shown above, the sum of the two is 0.060000000005.

This is undoubtedly a very serious problem, especially when the concurrency of e-commerce websites goes up, the problems will be huge. It may result in failure to place an order or problems in reconciliation. So next, we can use the BigDecimal class in Java to solve this kind of problem.

Popularize:

  • The precision of float in Java is 6-7 significant digits. The precision of double is 15-16 bits.

API

Constructor:

constructor describe
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.
BigDecimal(long)Creates an object with the long integer value specified by the parameter.
BigDecimal(String)Creates an object with the value specified by the parameter as a string.

Function:

methoddescribe
add(BigDecimal)Add the values in the BigDecimal object, and then return this 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)Divide the value in the BigDecimal object, and then return this object.
toString()Converts the value of a 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.

Due to the general numerical type, such as double, it can not accurately represent more than 16 digits.

BigDecimal precision is also lost

When we use BigDecimal, it makes sense to use its BigDecimal(String) constructor to create objects. For others, such as BigDecimal b = new BigDecimal(1), the problem of precision loss will still occur. The following code:

BigDecimal a = new BigDecimal(1.01);
BigDecimal b = new BigDecimal(1.02);
BigDecimal c = new BigDecimal("1.01");
BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b));
System.out.println(c.add(d));

Output:

2.0300000000000000266453525910037569701671600341796875
2.03

It can be seen that the loss of precision BigDecimal is more excessive. However, there is no such problem when using the variables of BigDecimal(String) constructor of BigDecimal.

The reason is that there are all the principles of computer composition, and their coding determines such a result.

long can accurately store 19 digits, while double can only prepare to store 16 digits.

Because double has exp bits, it can store more than 16 digits, but it needs to be at the expense of the inaccuracy of the low order. If you need precise storage of more than 19 digits, you must save it with BigInteger, which will of course sacrifice some performance.

Therefore, when we generally use BigDecimal to solve the problem of loss of precision in commercial operation, we must use it to construct a constructor of type with parameter String when declaring BigDecimal object.

At the same time, this principle is also mentioned in Effective Java and MySQL. float and double can only be used for scientific calculation and engineering calculation. In business operations, we use BigDecimal.

Moreover, we also give the official description from the comments of the source code. The following is a part of the comments on the constructor of the double type parameter of BigDecimal class:

* The results of this constructor can be somewhat unpredictable.  
* One might assume that writing {@codenew BigDecimal(0.1)} in  
* Java creates a {@code BigDecimal} which is exactly equal to  
* 0.1 (an unscaled value of 1, with a scale of 1), but it is  
* actually equal to  
* 0.1000000000000000055511151231257827021181583404541015625.  
* This is because 0.1 cannot be represented exactly as a  
* {@codedouble} (or, for that matter, as a binary fraction of  
* any finite length).  Thus, the value that is being passed  
* <i>in</i> to the constructor is not exactly equal to 0.1,  
* appearances notwithstanding.  
  ......  
   * When a {@codedouble} must be used as a source for a  
* {@code BigDecimal}, note that this constructor provides an  
* exact conversion; it does not give the same result as  
* converting the {@codedouble} to a {@code String} using the  
* {@link Double#toString(double)} method and then using the  
* {@link #BigDecimal(String)} constructor.  To get that result,  
* use the {@codestatic} {@link #valueOf(double)} method.  
* </ol>  
public BigDecimal(double val) {  
    this(val,MathContext.UNLIMITED);  
}  

The first paragraph also makes it clear that it can only calculate infinitely close to this number, but it can not be accurate to this number.

The second paragraph says that if you want to calculate this value accurately, you need to convert the parameter of double type into that of String type. And use the construction method of BigDecimal(String). To get results.

Correct use of BigDecimal

In addition, 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 parameters in the method must also be BigDecimal objects, which can also be seen from the API s listed just now.

In the general development process, the data stored in our database are of float and double types. It is very inconvenient to carry out the operation of taking and taking, which needs continuous transformation. Here I write a tool class:

//Source: https://mp.weixin.qq.com/s/_YyQyHPy_8j0OIzwqgjo5g
public class BigDecimalUtil {  

    private BigDecimalUtil() {}  
    public static BigDecimal add(double v1, double v2) {// v1 + v2  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.add(b2);  
    }  
    public static BigDecimal sub(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.subtract(b2);  
    }  
    public static BigDecimal mul(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        return b1.multiply(b2);  
    }  
    public static BigDecimal div(double v1, double v2) {  
        BigDecimal b1 = new BigDecimal(Double.toString(v1));  
        BigDecimal b2 = new BigDecimal(Double.toString(v2));  
        // 2 = keep two digits after the decimal point ROUND_HALF_UP = rounding  
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// Deal with inexhaustible situations  
    }  
}

This tool class provides basic addition, subtraction, multiplication and division operations of double type. Just call it directly.

Keywords: Java

Added by FidelGonzales on Mon, 07 Feb 2022 14:11:56 +0200