I'm overriding an equality method for an object. Let's say an odometer with a km variable stored as a double (along with some other variables not important for the example).
public class Odometer {
private double km;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(km);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Odometer other = (Odometer) obj;
if (Double.doubleToLongBits(km) != Double.doubleToLongBits(other.km))
return false;
return true;
}
}
Now, the comparison for the double variable as generated by Eclipse (along with the hash code) is an exact bit-wise comparison. However, I've been told to use a "epsilon" difference when comparing float or double values. I've even heard it phrased as "never use equality when comparing floats."
boolean equals(double x, double y, double epsilon) {
return x - y < epsilon;
}
The JUnit assertEquals
method for doubles bears this out:
assertEquals(double expected, double actual, double epsilon)
So, which comparison should I use here?
The Javadoc for the
equals
method states (emphasis mine):https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-
The equality method must be transitive. If you used an epsilon, this won't hold.
Consider double values x = 2.0, y = 2.6, z = 3.1, and epsilon = 1.0.
Note that z - y = 0.5 and y - x = 0.6, both of which are less than the epsilon of 1.0. However, z - x = 1.1 which is more than 1.0.
Hence, we would have "x equals y" and "y equals z" but not "x equals z", which breaks transitivity. The same would happen if these were instance variables of some of other object, such as the odometer in the above example.
Thus the equality should be exact. Converting to bits as above works, as does using
Double.compare(double d1, double d2)
or converting them to Double values and then usingDouble.compareTo(Double anotherDouble)
. Be aware that these will consider 0.0 and -0.0 as different numbers.https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#compare-double-double- https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#compareTo-java.lang.Double-
This is also important for the sake of keeping the hash function consistent.
Do not use the built-in Java equality operator
==
even for primitive double values. As stated in the JavaDocs on thecompareTo
method, equality fails withNaN
. (This StackOverflow question also has some more information: Why is Java's Double.compare(double, double) implemented the way it is?)One last point - this does not apply to the above example, since primitive double values are used, but if you use
Double
objects, remember to check fornull
before trying to pass them into any of the Double comparison functions.