当前位置:网站首页>Step on the pit. The BigDecimal was improperly used, resulting in P0 accident!

Step on the pit. The BigDecimal was improperly used, resulting in P0 accident!

2022-06-10 18:11:00 hello-java-maker

Recommended today

 Reduce  try-catch , This is grace !

 An addictive new generation of development artifacts , Say goodbye to Controller、Service、Dao Other methods 

SpringBoot Realize face recognition function 

 believe me , Use  Stream  Can really make the code more elegant !

 The most detailed thread pool in the whole network  ThreadPoolExecutor  Reading !

 Batch splitting with multithreading  List  Import database , Efficiency bars !

background

When we use the amount to calculate or display the amount, we often use BigDecimal, It is also a very recommended type when it comes to the amount .

BigDecimal It also provides many constructor methods , Improper use of these constructor methods may cause unnecessary trouble or even loss of money , Thus causing accident asset loss .

accident

Next, let's look at an accident at the cashier .

Problem description

The cashier reported an error in calculating the amount of goods , The order cannot be paid .

Accident level

P0

Accident process

as follows :

  • 13:44, Get the alarm , Order payment failed , Payment availability reduced to 60%

  • 13:50, Quickly roll back online code , Back to normal

  • 14:20,review Code , Problems found in pre release verification

  • 14:58, Modify the problem code to go online , Online recovery

The cause of the problem

BigDecimal Lost precision in amount calculation .

Cause analysis

First, let's use a piece of code to reproduce the root cause of the problem , As shown below :

public static void main(String[] args) {  
    BigDecimal bigDecimal=new BigDecimal(88);  
    System.out.println(bigDecimal);  
    bigDecimal=new BigDecimal("8.8");  
    System.out.println(bigDecimal);  
    bigDecimal=new BigDecimal(8.8);  
    System.out.println(bigDecimal);  
}

The results are as follows :

53ce4fa690d6886a145afe3444c8f202.png

Found by test , When using double perhaps float These floating-point data types , Will lose precision ,String、int Will not be , Why is that ?

Let's open the constructor method to see the source code :

public static long doubleToLongBits(double value) {  
    long result = doubleToRawLongBits(value);  
    // Check for NaN based on values of bit fields, maximum  
    // exponent and nonzero significand.  
    if ( ((result & DoubleConsts.EXP_BIT_MASK) ==  
          DoubleConsts.EXP_BIT_MASK) &&  
         (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)  
        result = 0x7ff8000000000000L;  
    return result;  
}

The problem is doubleToRawLongBits In this way , stay jdk in double class (float And int Corresponding ) Provided in double And long transformation ,doubleToRawLongBits Will be double Convert to long, This method is the original method ( The bottom is not java Realization , yes c++ Realized ).

double The reason why something goes wrong , It's because the decimal point to binary lost precision .

e99a604740525303b91ea8a3f8b1a5f1.png

BigDecimal Expand decimal number in processing N Times make it count on integers , And keep the corresponding precision information .

float and double type , It is mainly designed for scientific calculation and engineering calculation , The reason for performing binary floating-point operations , It is designed to provide accurate and fast approximate sum calculation over a wide range of numerical values .

It doesn't provide completely accurate results , So it should not be used for precise results .

When the floating-point number reaches a certain large number , Will automatically use scientific counting , Such a representation is only approximate to the real number but not equal to the real number .

When decimals are converted to binary, there will be infinite cycles or the length of the floating-point mantissa will be exceeded .

summary

therefore , In the process of precision calculation , We try to use String Type to convert .

The correct usage is as follows :

BigDecimal bigDecimal2=new BigDecimal("8.8");
BigDecimal bigDecimal3=new BigDecimal("8.812");
System.out.println( bigDecimal2.compareTo(bigDecimal3));
System.out.println( bigDecimal2.add(bigDecimal3));

BigDecimal What is created is an object , We can't use the traditional addition, subtraction, multiplication and division to calculate it , Must use his method , In our database store , If we use double perhaps float type , It needs to be calculated after round-trip conversion , Very inconvenient .

Tool sharing

So here we put together a util Class for everyone .

import java.math.BigDecimal;

/**
 * @Author shuaige
 * @Date 2022/4/17
 * @Version 1.0
 **/
public class BigDecimalUtils {
    public static BigDecimal doubleAdd(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }
    public static BigDecimal floatAdd(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.add(b2);
    }
    public static BigDecimal doubleSub(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 floatSub(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.subtract(b2);
    }

    public static BigDecimal doubleMul(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 floatMul(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.multiply(b2);
    }

    public static BigDecimal doubleDiv(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        //  Keep two decimal places  ROUND_HALF_UP =  rounding 
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    public static BigDecimal floatDiv(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        //  Keep two decimal places  ROUND_HALF_UP =  rounding 
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    /**
     *  Compare v1 v2 size 
     * @param v1
     * @param v2
     * @return v1>v2 return 1  v1=v2 return 0 v1<v2 return -1
     */
    public static int doubleCompareTo(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return  b1.compareTo(b2);
    }
    public static int floatCompareTo(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return  b1.compareTo(b2);
    }
}

source :juejin.cn/post/7087404273503305736

Last , Recommend a planet of my knowledge , Join now , front 100 name , It only needs  25 element that will do , Very favorable .

原网站

版权声明
本文为[hello-java-maker]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101720248386.html