web123456

Double type accuracy loss problem and solution

Double type accuracy loss problem:

(1) Addition operation.

public static void main(String[] args) {
	 double number1 = 1;
	 double number2 = 20.2;
	 double number3 = 300.03;
	 double result = number1 + number2 + number3;
	 ("Use double operation result: "+result);
 }

The printing results are as follows:

The result of using double operation: 321.229999999999999999996. . .

(2) Subtraction operation.

double d1 = 2.11;
double d2 = 2.10;
( d1 - d2);

The result of the oral calculation is 0.01. However, the result of execution using the program was unexpected, with the execution result being 0.00999999999999999999787.

(3) Multiplication operation.

public static void main(String[] args) {
     double a = 152.70;
     (a);
     (a * 100);//15280.000000000000002
     ((a * 100));//It's okay
 
     double b = 152.70;
     (b);
     (b * 100);//15269.9999999999999999998
     ((int)(b * 100));//15269 Error!!! Not what you want
 }

Then return the result of 15280.000000000002 - 15280 = 1.8189894035458565E-12 to the front end,

The front end is compared with 0, and it turns out to be exploded

Reason: It is the accuracy problem of double. A single double number may have loss of accuracy. Multiple double number operations (both subtraction and multiplication are inputted into addition) may lead to more loss of accuracy. . .

Why does double type experience precision loss problem?


We know that computers have been developing for such a long time, but it can always recognize 0 and 1 (i.e. binary). No matter which programming language we use or in which compilation environment we work, we must first translate the source code into binary machine code before it can be recognized by the computer.

To give a simple example, in the source program 2.4, it is in decimal, but the computer cannot directly recognize it, so it must be compiled into binary first.

Then the question is, the binary of 2.4 is not the exact 2.4, but the closest binary representation is 2.3999999999999996.

The reason isA floating point number consists of two parts: the exponent and the mantissa.If you know how to convert binary and decimal floating-point numbers, it should not be difficult to understand.

If floating point numbers participate in the calculation during this conversion process, then they will become unpredictable and irreversible during the conversion process. We have reason to believe that in this process, the loss of accuracy occurs.

As for why some floating-point calculations get accurate results, it should be that it happens that the calculation can accurately convert between binary and decimal.

And when outputting a single floating point data, it can be output correctly, such as:

Double num3 = 2.4;
(num3);

The output result is 2.4, not 2.3999999999999996. In other words, when floating point calculations are not performed, floating point numbers can be displayed correctly in decimal.

This confirms the above statement that if floating-point numbers participate in the calculation, the conversion process between floating-point binary and decimal will become unpredictable and irreversible.

The conclusion is,In Java, floating point numbers are inaccurate because the computer cannot be expressed accurately with binary decimals.

Classic problem: floating point accuracy is lost

The problem of accuracy loss is that it will also occur in other computer languages. Data of float and double type do not provide completely accurate results when performing binary floating point operations.

The error occurs not lies in the size of the number, but in the accuracy of the number.

Regarding the issue of missing accuracy of floating-point storage, the topic is too huge. Interested students can search for it by themselves:【Solve the Confusion】Analysis of the memory storage and accuracy loss of float type

Here we briefly discuss why the accuracy loss occurs in decimal numbers to binary numbers. Decimal numbers are divided into integer parts and decimal parts. Let’s take a look at them separately to know the reason:

How to convert decimal integers into binary integers?
Divide the divisor by 2 each time, and stop the process as long as the quotient is 0.

5 / 2 = 2 More than 1

2 / 2 = 1 More than 0

1 / 2 = 0 More than 1

// The result is 101

This algorithm will never loop infinitely, integers can always be represented accurately using binary numbers, but what about decimals?

How to convert decimal decimals into binary numbers?
Each time, multiply the decimal part by 2, take out the integer part. If the decimal part is 0, you can stop this process

0.1 * 2 = 0.2 Take the integer part 0

0.2 * 2 = 0.4 Take the integer part 0

0.4 * 2 = 0.8 Take the integer part 0

0.8 * 2 = 1.6 Take the integer part 1

0.6 * 2 = 1.2 Take the integer part 1

0.2 * 2 = 0.4 Take the integer part 0

//... I think I don't have to write this anymore. You should have found that the above process has started to loop, and the decimal part can never be 0

This algorithm has a certain probability that there will be an infinite loop, that is, it is impossible to use finite length binary numbers to represent decimal decimals, which is the reason for the accuracy loss problem.

Floating point numbers are not suitable for precise calculations, but for scientific calculations.

The float and double types are used to represent numbers with decimal points. So why don’t we call them decimal or real numbers, but floating point numbers? This is because these numbers are stored in the form of scientific notation.

When a number is like 50.534, the form converted into scientific notation is 5.053e1, its decimal point moves to a new position (i.e. it floats).

It can be seen that floating point numbers are originally used for scientific calculations, and it is really inappropriate to use them for accurate calculations.

How to solve double precision problem with BigDecimal?


We have understood why there is a loss of accuracy, so we should know that when a business scenario has very high requirements for the accuracy of double data, some means must be taken to deal with this problem.

This is alsoBigDecimalWhy is it widely used in the amount payment scenario?

The BigDecimal class is located under the package and is used to perform precise operations on numbers with more than 16 significant bits.

Generally speaking, variables of double type can handle 16-bit significant numbers.

But in actual applications, if there are more than 16 bits, the BigDecimal class is needed to operate.

new BigDecimal(double val)
This method is unpredictable. Taking 0.1 as an example, do you think you passed a double type 0.1 and will finally return a BigDecimal with a value of 0.1? No, the reason is that 0.1 cannot be represented by a binary number of finite length, and cannot be expressed accurately as a double number, and the final result will be 0.100000xxx.

new BigDecimal(String val)
This method is completely predictable, that is, if you pass in a string "0.1", it will return you a BigDecimal with a value of 0 and 1. The official also said that if you can use this constructor, use this constructor.

(double val)
The second construction method is already excellent enough, but you still want to pass in a double value. What should you do? The official actually provides you with ideas and implements it. You can use (double val) to convert the double value to String first, and then call the second construction method. You can directly use the static method: valueOf(double val).

        Summary: When converting a double to BigDecimal, you need to convert the double to a string first, and then use it as a parameter of the BigDecimal(String val) constructor to avoid accuracy problems.

​​​​​​​

The following is the double precise calculation tool class provided (if the sum of calculated values ​​is very large and exceeds 50 0000, please use the method with ToStr, otherwise the double number will be converted into scientific notation to display)

package .common_library;
 
 import ;
 
 public class BigDecimalManager {
 
     /**
      * Addition operation of double type (no rounding required)
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double additionDouble(double m1, double m2) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).doubleValue();
     }
 
     /**
      * Addition operation of double type (need to round, retain three decimal places)
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double additionDouble(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
     }
 
     /**
      * Extra large numerical addition operation of double type (need to round, retain three decimal places)
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static String additionDoubleToStr(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();
     }
 
     /**
      * Double type subtraction operation
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double subtractionDouble(double m1, double m2) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).doubleValue();
     }
 
     /**
      * Double type subtraction operation (need to round, retain three decimal places)
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double subtractionDouble(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
     }
 
     /**
      * Extra large numerical subtraction operation of double type (need to round, retain three decimal places)
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static String subtractionDoubleToStr(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();
     }
 
     /**
      * Double type multiplication operation
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double multiplicationDouble(double m1, double m2) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).doubleValue();
     }
 
     /**
      * Double type multiplication operation
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double multiplicationDouble(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
     }
 
     /**
      * Multiplication operation of super large numeric values ​​of double type
      * @param m1
      * @param m2
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static String multiplicationDoubleToStr(double m1, double m2, int scale) {
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString();
     }
 
     /**
      * Double type division operation
      * @param m1
      * @param m2
      * @param scale
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static double divisionDouble(double m1, double m2, int scale) {
         if (scale < 0) {
             throw new IllegalArgumentException("Parameter error");
         }
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
     }
    
     /**
      * Dividing operation of super large numerical values ​​of double type
      * @param m1
      * @param m2
      * @param scale
      * @return If doubleValue() is not added, the BigDecimal object is returned
      */
     public static String divisionDoubleToStr(double m1, double m2, int scale) {
         if (scale < 0) {
             throw new IllegalArgumentException("Parameter error");
         }
         BigDecimal p1 = new BigDecimal((m1));
         BigDecimal p2 = new BigDecimal((m2));
         return (p2, scale, BigDecimal.ROUND_HALF_UP).toPlainString();
     }
 
 }

·Test and verify whether the problem is solved:

public static void main(String[] args) {
	 double result1 = additionDouble(1, 20.2);
	 double result2 = additionDouble(result1, 300.03);
	 ("Use BigDecimal operation result: " + result2);
 }

The results are as follows:

Using BigDecimal operation result: 321.23

Perfectly solves the problem of loss of accuracy during double type data addition and subtraction operations.

If you have any questions about this, please contact qq1164688204.

Recommended Android open source project

Project function introduction: RxJava2 and Retrofit2 projects, add automatic management token function, add RxJava2 life cycle management, use App architecture to design the MVP mode and MVVM mode, and use componentization at the same time, and some codes use Kotlin. This project is continuously maintained.

Project address: /urasaki/RxJava2AndRetrofit2