## How Java compares floating point numbers correctly

Flat sauce 2020-11-08 20:42:37
java compares floating point numbers

Look at the following code , take d1 and d2 Compare two floating-point numbers , What will the output be ？

``````double d1 = .1 * 3;
double d2 = .3;
System.out.println(d1 == d2);
``````

According to normal logic ,d1 After calculation, the result should be `0.3`, The final printed result should be `true`, Right ？ But run it and you will find that the result is not `true` It is `false` .

Output it `d1`, The answer is not imagined `0.3` It is `0.30000000000000004`, So and `d2` The result of comparison is naturally `false`

How to correctly compare floating-point numbers （ Single precision float And double precision double）, Not just Java Specific questions , In the computer's memory , Floating point numbers are stored using IEEE 754 standard , There will be a problem of precision .

Floating point numbers are easy to cause some small rounding errors in the process of storage and conversion , That's why , When comparing floating-point numbers , Out of commission “==” The operator —— To require strict equality .

So how to correctly compare floating-point numbers ？ There are two options .

The first is to allow a little error between the two values （ Specify a threshold ）, Use `Math.abs()` Method to calculate the absolute value of the difference between two floating-point numbers , If the difference is within the threshold , We think that two floating-point numbers are equal .

``````final double THRESHOLD = .0001;
double d1 = .1 * 3;
double d2 = .3;
if(Math.abs(d1-d2) < THRESHOLD) {
System.out.println("d1 and d2 equal ");
} else {
System.out.println("d1 and d2 It's not equal ");
}
``````

`Math.abs()` Method is used to return double The absolute value of , If double Less than 0, Then return to double Positive value of , Otherwise return to double. in other words ,`abs()` After the result is absolutely greater than 0, If the result is less than the threshold （THRESHOLD）, We just think d1 and d2 equal .

The second option is to use the BigDecimal class , You can specify the mode and precision to round , In this way, the rounding error can be solved .

To use the BigDecimal Class `compareTo()` Methods compare two numbers , This method will ignore the number of digits after the decimal point , How to understand this sentence ？ for instance 2.0 and 2.00 The number of digits is different , But the two values are equal .

`a.compareTo(b)` If a and b equal , Then return to 0, Otherwise return to -1.

tips: Do not use `equals()` Methods for two BigDecimal Object to compare , This is because `equals()` The method takes into account the number of digits , If the number of digits is different , Will return false, Even though the mathematical values are equal .

``````BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");
System.out.println(a.equals(b));
System.out.println(a.compareTo(b) == 0);
``````

In the above code `a.equals(b)` The result is false, because 2.00 and 2.0 The number of digits after the decimal point is different , but `a.compareTo(b) == 0` The result is true, because 2.00 and 2.0 At the mathematical level, the values are indeed equal .

`compareTo()` The process of method comparison is very rigorous , Source code is as follows ：

``````private int compareMagnitude(BigDecimal val) {
// Match scales, avoid unnecessary inflation
long ys = val.intCompact;
long xs = this.intCompact;
if (xs == 0)
return (ys == 0) ? 0 : -1;
if (ys == 0)
return 1;
long sdiff = (long)this.scale - val.scale;
if (sdiff != 0) {
// Avoid matching scales if the (adjusted) exponents differ
long xae = (long)this.precision() - this.scale; // [-1]
long yae = (long)val.precision() - val.scale; // [-1]
if (xae < yae)
return -1;
if (xae > yae)
return 1;
if (sdiff < 0) {
// The cases sdiff <= Integer.MIN_VALUE intentionally fall through.
if ( sdiff > Integer.MIN_VALUE &&
(xs == INFLATED ||
(xs = longMultiplyPowerTen(xs, (int)-sdiff)) == INFLATED) &&
ys == INFLATED) {
BigInteger rb = bigMultiplyPowerTen((int)-sdiff);
return rb.compareMagnitude(val.intVal);
}
} else { // sdiff > 0
// The cases sdiff > Integer.MAX_VALUE intentionally fall through.
if ( sdiff <= Integer.MAX_VALUE &&
(ys == INFLATED ||
(ys = longMultiplyPowerTen(ys, (int)sdiff)) == INFLATED) &&
xs == INFLATED) {
BigInteger rb = val.bigMultiplyPowerTen((int)sdiff);
return this.intVal.compareMagnitude(rb);
}
}
}
if (xs != INFLATED)
return (ys != INFLATED) ? longCompareMagnitude(xs, ys) : -1;
else if (ys != INFLATED)
return 1;
else
return this.intVal.compareMagnitude(val.intVal);
}
``````

Next , use BigDecimal To solve the initial problem .

``````BigDecimal d1 = new BigDecimal("0.1");
BigDecimal three = new BigDecimal("3");
BigDecimal d2 = new BigDecimal("0.3");
d1 = d1.multiply(three);
System.out.println("d1 = " + d1);
System.out.println("d2 = " + d2);
System.out.println(d1.compareTo(d2));
``````

The output of the program is as follows ：

``````d1 = 0.3
d2 = 0.3
0
``````

d1 and d2 All for 0.3, therefore `compareTo()` The result is 0, Indicates that two values are equal .

To sum up , In the case of floating-point numbers , Never use `==` Operator to compare , Because of the accuracy problem . Or use the threshold to ignore the rounding problem , Or use BigDecimal To replace double perhaps float.