Interview: Does BigDecimal definitely not lose precision?

Preface

We all know that floating-point variables will lose precision when calculating. The following piece of 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);

输出:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

It can be seen that when performing floating-point arithmetic in Java, the problem of losing precision will occur. Then, if we are calculating commodity prices, problems will arise. It is very likely that we have 0.06 yuan in our hands, but we cannot buy a product of 0.05 yuan and a 0.01 yuan. Because as shown above, the sum of the two of them is 0.060000000000000005. This is undoubtedly a very serious problem, especially when the concurrency of e-commerce websites increases, 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 digits.

API

Constructor:

Constructor Description                       
  BigDecimal(int) Creates an object with the integer value specified by the parameter.      
  BigDecimal(double) creates an object with the double-precision 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 represented by the string specified by the parameter.


function:

 Method Description                          
  add(BigDecimal) Add the values ​​in the BigDecimal object, and then return this object. 
  subtract(BigDecimal) Subtract the value in the BigDecimal object, and then return this object. 
  multiply(BigDecimal) Multiply the values ​​in the BigDecimal object, and then return this object. 
  divide(BigDecimal) Divide the value in the BigDecimal object, and then return this object. 
  toString() Converts the numeric value of the BigDecimal object into a string.    
  doubleValue() returns the value in the BigDecimal object as a double precision number.   
  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 general numeric types, for example, double cannot accurately represent numbers with 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. Others, such as BigDecimal b = new BigDecimal(1), still have the problem of loss of precision. 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));

输出:
2.0300000000000000266453525910037569701671600341796875
2.03


It can be seen that the loss of precision BigDecimal is even more excessive. However, when using BigDecimal's BigDecimal(String) constructor variables, there is no such problem. The reason is contained in the principles of computer composition, and their coding determines this result. Long can accurately store 19 digits, while double can only store 16 digits. Because double has exp bits, it can store more than 16 digits, but it needs to be at the cost of low-order inaccuracy. If you need accurate storage of more than 19 digits, you must use BigInteger to save, of course, some performance will be sacrificed. Therefore, when we generally use BigDecimal to solve the problem of loss of precision in commercial operations, we must use it to construct a constructor whose parameter is String when declaring a BigDecimal object.

At the same time, this principle is also mentioned in Effective Java and MySQL Must Know Must Know. Float and double can only be used for scientific calculations and engineering calculations. We need to use BigDecimal in business computing.

And we officially gave instructions from the comments of the source code. The following is a part of the annotations on the constructor of the double type parameter of the BigDecimal class:

* The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new 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
     * {@code double} (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 {@code double} 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 {@code double} 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 {@code static} {@link #valueOf(double)} method.
     * </ol>
public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}


第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。第二段则说,如果要想准确计算这个值,那么需要把double类型的参数转化为String类型的。并且使用BigDecimal(String)这个构造方法进行构造。 去获取结果。

正确运用BigDecimal

另外,BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象,由刚才我们所罗列的API也可看出。

在一般开发过程中,我们数据库中存储的数据都是float和double类型的。在进行拿来拿去运算的时候还需要不断的转化,这样十分的不方便。这里我写了一个工具类:

/**
 * @author: Ji YongGuang.
 * @date: 19:50 2017/12/14.
 */
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 = 保留小数点后两位   ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况
    }
}


该工具类提供了double类型的基本的加减乘除运算。直接调用即可。

最后

感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。


Guess you like

Origin blog.51cto.com/14849432/2540536