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 becauseequals()
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.