2

An object of class Foo is considered equal if the double members are within a given range of the other object. Such an error can easily be introduced due to floating point arithmetic.

The method isDoubleEquals and doubleArrayEquals will take care of the equals part but the contract states that the hashcode has to be identical for equal objects. The default hashcode of doubles will not map close values to the same value, therefore what would be a good approach to get the same hash value for matching doubles?

public class Foo {

    double[] values;

    public Foo(double[] values) {
        this.values = values;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        //TODO Arrays.hashCode will not work with the contract
        result = prime * result + Arrays.hashCode(values);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Foo other = (Foo) obj;
        if (!doubleArrayEquals(values, other.values,1e-10))
            return false;
        return true;
    }

    private boolean doubleArrayEquals(double[] arr, double[] arr2, double epsilon) {
        if (arr== arr2)
            return true;
        if (arr == null || arr2 == null)
            return false;

        int length = arr.length;
        if (arr2.length != length)
            return false;

        for(int i = 0; i < length; i++) {
            if(!isDoubleEquals(arr[i],arr2[i],epsilon)) {
                return false;
            }
        }
        return true;
    }

    private boolean isDoubleEquals(double needle, double target, double epsilon) {
        return Math.abs(needle - target) <= epsilon;
    }
}
Kilian
  • 1,540
  • 16
  • 28
  • 1
    "therefore what would be a good approach to get the same hash value for matching doubles?" Round them to the nearest integer, mod some prime number – Michael Dec 17 '18 at 09:27
  • @Michael good idea. Since I am working with a lot of small values which will be within a tiny range the distribution of the hashes is critical as well. I probably have to round the double value to the exponents decimal place and then calculate the mod. Let me think about it a little bit :-) – Kilian Dec 17 '18 at 09:32

1 Answers1

6

You cannot properly write equals and hashCode methods which deal with approximate equality.

The contract of equals requires transitivity, and approximate equality is not transitive.

That's not to say that approximate equality is not a useful thing: it's just not what Java's equals (and hashCode) methods are for, and therefore you should define your own method - say, isApproximatelyEqualTo - to support it, without overloading the well-known Java methods.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • With transitivity in mind I understand the issue. Thanks. Equals and hashcode where chosen since the objects are added in a hashset to find duplicates. Sadly adding a different method will not help in this instance. – Kilian Dec 17 '18 at 09:43
  • 1
    So, how many duplicates are there in the case of three single-element arrays containing `[0]`, `[1e-10]` and `[2e-10]`? If you add `[1e-10]` first, then `[0]` and `[2e-10]` are duplicates; if you add `[0]` first, then `[1e-10]` is a duplicate, but `[2e-10]` is not. – Andy Turner Dec 17 '18 at 09:45
  • For one of the problems it's 1e-10 about 90% of the cases at the end. This is part of a genetic algorithm whose results will eventually converge and are bound to be in very close proximity at some point – Kilian Dec 17 '18 at 09:50