Look at the phenomenon

When dealing with floating-point data such as a float or a double, there are some odd things that happen occasionally.

Typical phenomenon (a) : conditional judgment exceeds expectation

System.out.println( 1f= =0.9999999 f );   // Print: false
System.out.println( 1f= =0.99999999 f );  // Print: true nani?
Copy the code

Typical phenomenon (2) : data conversion exceeds expectations

float f = 1.1 f;
double d = (double) f;
System.out.println(f);  // Print: 1.1
System.out.println(d);  // Print: 1.100000023841858 Nani?
Copy the code

Typical phenomenon (3) : the basic operation exceeds the expectation

System.out.println( 0.2 + 0.7 );  

// Print: 0.8999999999999999 Nani?
Copy the code

Typical phenomenon (IV) : data increase exceeds expectations

float f1 = 8455263f;
for (int i = 0; i < 10; i++) {
    System.out.println(f1);
    f1++;
}
// Print: 8455263.0
// Print: 8455264.0
// Print: 8455265.0
// Print: 8455266.0
// Print: 8455267.0
// Print: 8455268.0
// Print: 8455269.0
// Print: 8455270.0
// Print: 8455271.0
// Print: 8455272.0

float f2 = 84552631f;
for (int i = 0; i < 10; i++) {
    System.out.println(f2);
    f2++;
}
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
// Print: 8.4552632E7 Nani? Isn't that plus one?
Copy the code

As you can see, these simple scenarios are hard to use, so there are a lot of hidden pitfalls in dealing with floating-point numbers (including doubles and floats).

Anyone who uses double/float data in transactions such as commodity value, order transaction, and currency calculation will be sent packing.


Why?

Let’s take the first typical phenomenon for example:

System.out.println( 1f= =0.99999999 f );
Copy the code

Compare 1 with 0.99999999 and print true!

What does that tell us? That means the computer can’t tell the difference between the two numbers. Why is that?

Let’s think about it briefly:

We know that the input of these two floating point numbers are only concrete values that we human eyes can see, which are generally understood by us as decimal numbers, but the bottom computer is not calculated in accordance with decimal numbers, and those who have learned the basic principle of counting and grouping know that, The bottom of the computer is ultimately based on 0, 1 binary like 010100100100110011011 to complete.

So to understand what’s really going on, we should take a look at these two decimal floating point numbers in binary space.

How to convert decimal floating point number to binary, how to calculate, I think this should belong to the basic computer base conversion, in the “Principles of Computer Composition” similar class must learn, I will not repeat this, directly give the result (convert it to IEEE 754 Single Precision 32-bit, The precision of the float type.

1.0 (decimal) ↓ 00111111 10000000 00000000 00000000 (binary) ↓ 0x3F800000 (hexadecimal)Copy the code
0.99999999 (decimal) ↓ 00111111 10000000 00000000 00000000 (binary) ↓ 0x3F800000 (hexadecimal)Copy the code

Sure enough, the underlying binary representation of the two decimal floating-point numbers is exactly the same, so it’s no wonder that == returns true!

But 1f == 0.9999999f returns the expected result, print false, let’s also convert them to binary mode to see what happens:

1.0 (decimal) ↓ 00111111 10000000 00000000 00000000 (binary) ↓ 0x3F800000 (hexadecimal)Copy the code
0.9999999 (decimal) ↓ 00111111 01111111 11111111 11111110 (binary) ↓ 0x3F7FFFFE (hexadecimal)Copy the code

Well, obviously, they do have different binary representations, as it should.

So why is the underlying binary representation of 0.99999999:00111111 10000000 00000000 00000000?

Isn’t this the binary representation of floating point 1.0?

This is about to talk about the accuracy of floating points.


Floating-point accuracy problem!

If you have taken principles of Computer Composition, you may know that floating-point numbers are stored in a computer in accordance with IEEE 754 floating-point counting standard, which can be expressed in scientific notation:

Given the symbol (S), step (E), and mantissa (M) dimensions, the representation of a floating-point number is completely determined, so float and double are stored in memory as follows:

1. Symbol Part (S)

0-1 – negative

2. Step code part (E) (Index part) :

  • forfloatType floating point number, exponential part8Digit, which can be positive or negative, so the range of exponents that can be represented is- 127 ~ 128
  • fordoubleType floating point number, exponential part11Digit, which can be positive or negative, so the range of exponents that can be represented is- 1023 ~ 1024

3. Mantissa (M) :

The precision of a floating point number is determined by the number of mantissa digits:

  • forfloatType floating point, mantissa part23Bit, in decimal^ 2 = 8388608, so the decimal accuracy is only6 ~ 7A;
  • fordoubleType floating point, mantissa part52Bit, in decimal52 ^ 2 = 4503599627370496, so the decimal accuracy is only15 to 16position

So the value 0.99999999f above is clearly beyond the precision range of float data, and problems are inevitable.


How to solve the accuracy problem

So what about high-precision scenarios involving commodity amounts, transaction values, currency calculations, etc.?

Method 1: use strings or arrays to solve the multi-digit problem

For those of you who have studied algorithms in school, it is a typical idea to use strings or arrays to represent large numbers.

For example, the classic interview questions: write two arbitrary large numbers of addition, subtraction, multiplication and other operations.

At this time we can use string or array to represent this large number, and then according to the four rules of operation to manually simulate the specific calculation process, the middle also need to consider all kinds of problems such as: carry, borrow, symbol and so on processing, is really very complex, this article will not be repeated.

Method two: Java’s large number classes are a good thing

The JDK has already considered the accuracy of floating-point calculations for us, so it provides a large number class dedicated to high-precision numerical calculations.

As mentioned in the previous article, “To tell you the truth, I recently got into trouble with Java source code”, Java’s large number classes are located under the java.math package:

As you can see, the commonly used BigInteger and BigDecimal are great tools for handling high-precision numerical calculations.

BigDecimal num3 = new BigDecimal( Double.toString( 0.1 f)); BigDecimal num4 =new BigDecimal( Double.toString( 0.99999999 f)); System.out.println( num3 == num4 );/ / print false

BigDecimal num1 = new BigDecimal( Double.toString( 0.2)); BigDecimal num2 =new BigDecimal( Double.toString( 0.7));/ / add
System.out.println( num1.add( num2 ) );  // Print: 0.9

/ /
System.out.println( num2.subtract( num1 ) );  // Print: 0.5

/ / by
System.out.println( num1.multiply( num2 ) );  // Print: 0.14

/ / in addition to
System.out.println( num2.divide( num1 ) );  // Print: 3.5
Copy the code

Of course, large numbers like BigInteger and BigDecimal are certainly not as efficient and expensive as the native types, and the choice depends on the actual situation.