Should Equality Comparison of Float / Double Insta

2020-04-14 11:46发布

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?

1条回答
Viruses.
2楼-- · 2020-04-14 12:24

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.

查看更多
登录 后发表回答