Floating Point Calculation in Java
Today, we encountered a problem in numerical calculation. The program is as follows:
Double A = (3.3-2.4)/0.1;
System. Out. println ();
You may think the result is very simple, not 9. Actually, the result is 8.999999998. Why? I read some documents and finally found out the reason.
Why do floating point numbers lose precision?
The binary representation of the decimal number may not be accurate enough.
It is not uncommon for floating-point numbers or double-precision floating-point numbers to be accurately expressed. The reason why floating point values cannot be accurately expressed in decimal format is that the CPU represents a floating point number. In this way, you may sacrifice some precision, and some floating point operations may introduce errors. For example, the binary representation of 2.4 is not the exact 2.4. Instead, the most close binary representation is 2.3999999999999999. The reason is that floating point numbers are composed of two parts: the index and the ending number. The value of a floating point is calculated by a specific mathematical formula. You may encounter loss of precision in any operating system or programming environment.
Note: You can use the binary coded decimal (BCD) library to maintain accuracy. The BCD numeric encoding method separately encodes each decimal digit.
Type Mismatch
You may have mixed floating-point numbers and double-precision floating-point numbers. Make sure that all data types are the same during mathematical operations.
Note: Float variables have only 7-bit precision, while double variables have 15-bit precision.
How to calculate the floating point precision?
The float and double types of simple floating point numbers in Java cannot be computed. Not only Java, but also in many other programming languages. In most cases, the calculation results are accurate, but you can try multiple times (you can do a loop) to try out errors similar to the above. Now we finally understand why we need BCD code.
This problem is quite serious. If you have 9.999999999999 yuan, your computer will not think you can buy 10 yuan of goods.
Some Programming Languages provide special currency types to handle this situation, but Java does not. Now let's take a look at how to solve this problem.
Rounding
Our first response was rounding. The round method in the math class cannot be set to retain a few decimal places. We can only keep two places like this ):
Public double round (double value ){
Return math. Round (value * 100)/100.0;
}
Unfortunately, the code above does not work normally. If you pass 4.015 to this method, it will return 4.01 instead of 4.02, as we can see above
4.015*100 = 401.49999999999994
Therefore, if we want to perform precise rounding, we cannot use simple types for any operation.
Java. Text. decimalformat cannot solve this problem either:
System. Out. println (New java. Text. decimalformat ("0.00"). Format (4.025 ));
The output is 4.02
Bigdecimal
This principle is also mentioned in objective java. Float and double can only be used for scientific computing or engineering computing. In commercial computing, java. Math. bigdecimal is used. Bigdecimal has a total of four creation methods. We don't care about the two that can be created using biginteger. There are two other methods:
Bigdecimal (double Val)
Translates a double into a bigdecimal.
Bigdecimal (string Val)
Translates the string repre sentation of a bigdecimal into a bigdecimal.
The Brief description of the above API is quite clear, and it is usually easier to use the above one. We may use it if we don't want it. What's the problem? When a problem occurs, the detailed description of the above method is as follows:
Note: The results of this constructor can be somewhat unpredictable. one might assume that new bigdecimal (. 1) is exactly equal. 1, but it is actually equal. 1000000000000000055511151231257827021181583404541015625. this is so because. 1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length ). thus, the long value that is being passed in to the constructor is not exactly equal. 1, appearances nonwithstanding.
The (string) constructor, on the other hand, is perfectly predictable: New bigdecimal (". 1 ") is exactly equal. 1, as one wocould CT. therefore, it is generally recommended that the (string) constructor be used in preference to this one.
It turns out that if we need precise calculation, we have to use string to create bigdecimal! The example in objective Java uses string to create bigdecimal, but this is not emphasized in the book. This may be a small mistake.
Solution
Now we can solve this problem. The principle is to use bigdecimal and must use string to create it.
But imagine, if we want to do an addition operation, we need to first convert two floating point numbers into strings, and then convert them into bigdecimal. Call the add method on one of them and input another as the parameter, then convert the result of the operation (bigdecimal) to a floating point number. Can you endure this cumbersome process? The following provides a tool class Arith to simplify operations. It provides the following static methods, including addition, subtraction, multiplication, division, and rounding:
Public static double add (double V1, double V2)
Public static double sub (double V1, double V2)
Public static double MUL (double V1, double V2)
Public static double Div (double V1, double V2)
Public static double Div (double V1, double V2, int scale)
Public static double round (Double V, int scale)
Appendix
Source File Arith. Java:
Import java. Math. bigdecimal;
Public class Arith {
// Default division operation precision
Private Static final int def_div_scale = 10;
// This class cannot be instantiated
Private Arith (){
}
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). doublue ();
}
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). doublue ();
}
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). doublue ();
}
Public static double Div (double V1, double V2 ){
Return Div (V1, V2, def_div_scale );
}
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). doublue ();
}
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). doublue ();
}
};
For double D = 2.4;
System. Out. println (d); // output 2.4, but not 2.3999999999999999?
After reading some materials, when a single double-type value is output, it can be correctly displayed in decimal format. Why? I am not familiar with it, but I am doing floating point computing, floating-point calculation refers to the calculation of floating-point numbers. This operation is usually accompanied by an approximate or rounding because it cannot be accurately expressed. It may be related to floatingdecimal (d). tojavaformatstring () of the double. tostring method.
I guess it is about 2.3999999999999999 that exceeds the output precision, so the reason is truncated.