3

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?

Mark Chimes
  • 295
  • 1
  • 11
  • 2
    A relevant question (given that any class overriding `equals` must also implement `hashCode()`) is: how do you implement `hashCode()` for fuzzy equality? You can't, other than by using a constant hash code, which potentially leads to awful performance. – Andy Turner Jul 17 '19 at 10:12

1 Answers1

6

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 equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

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 using Double.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 the compareTo method, equality fails with NaN. (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 for null before trying to pass them into any of the Double comparison functions.

Mark Chimes
  • 295
  • 1
  • 11
  • 1
    I searched for an answer, and could not find one, and then spent some time figuring out the answer myself. I figured I should document it here. If there's a question addressing this, I'll accept your criticism. Otherwise, I believe this is entirely in the spirit of StackOverflow's [Answer your own question](https://stackoverflow.com/help/self-answer) policy. – Mark Chimes Jul 17 '19 at 10:08
  • Good question and excellent answer. At first sight, I can't find a duplicate that addresses the use of an epsilon in an `equals()` implementation. – Robby Cornelissen Jul 17 '19 at 10:13
  • 2
    @RobbyCornelissen [I did find one](https://stackoverflow.com/questions/53812230/how-to-write-a-proper-hashcode-method-for-double-values-with-limited-precision) - in fact, I'd even commented on it - but, yeah, this is better. – Michael Jul 17 '19 at 10:15
  • @Michael Yup, close enough. Good find. – Robby Cornelissen Jul 17 '19 at 10:17
  • 1
    @MarkChimes I was technically right but the quality of the question is much lower than here, which I wasn't really expecting. If you had found it first then you could have cleaned it up rather than posting separately but no matter. I will just close that one as a dupe of this. – Michael Jul 17 '19 at 10:22