7

I am overriding equals and hashCode in a class that includes double fields. My first approach was to use the epsilon test in the equals method, and Double.hashCode( double ) in hashCode, but that can result in equal objects having different hash codes; here is a simplified example:

public class DoubleHashTest2
{
    public static void main(String[] args)
    {
        double  base1   = .9;
        double  base2   = .7;
        Test    test1   = new Test( base1 - .1 );
        Test    test2   = new Test( base2 + .1 );

        System.out.println( test1.equals( test2 ) );
        System.out.println( test1.hashCode() );
        System.out.println( test2.hashCode() );
    }

    private static class Test
    {
        private double  dnum1;

        public Test( double dnum1 )
        {
            this.dnum1 = dnum1;
        }

        public boolean equals( Test other )
        {
            final double    epsilon = .0001;
            boolean         result  = false;
            if ( this == other )
                result = true;
            else if ( other == null )
                result = false;
            else
                result  = Math.abs( this.dnum1 - other.dnum1 ) < epsilon;
            return result;
        }

        public int hashCode()
        {
            int hash    = Double.hashCode( dnum1 );
            return hash;
        }
    }
}

I've thought of several solutions, including converting to BigDecimal, but I'm not really happy with any of them. I finally settled on rounding:

public boolean equals( Test other )
{
    boolean         result  = false;
    if ( this == other )
        result = true;
    else if ( other == null )
        result = false;
    else
    {
        double  test1   = round( dnum1 );
        double  test2   = round( other.dnum1 );
        result  = test1 == test2;
    }
    return result;
}

public int hashCode()
{
    double  temp    = round( dnum1 );
    int hash    = Double.hashCode( temp );
    return hash;
}

private double round( double dnum )
{
    // tests for NaN and +/-infinity omitted for brevity
    final int       places      = 4;
    final double    round_const = Math.pow( 10, places );
    double result   = ((int)(dnum * round_const + .5)) / round_const;
    return result;
}

But choosing a good rounding algorithm is difficult, and this seems kind of expensive. I looked at similar classes, such as Point2D.Double, but equals in this class fails, for example, when comparing .8 and 0.7999999999999999.

Is there a recommended way for dealing with this issue?

Jack Straub
  • 452
  • 3
  • 13
  • 1
    Yes: give up on double equality; stop using equals and hashCode if you want matches that aren't 100% exactly matching. Any attempt to make that work is doomed. – Louis Wasserman Sep 28 '17 at 20:19
  • 2
    Your method definition `equals(Test)` is incorrect. Equals must be overridden from `Object` as `equals(Object)` – Beno Sep 28 '17 at 20:22

3 Answers3

1

Answering the main question

You don't need any custom rounding, as Double class has doubleToLongBits() method, which simply converts double to long (both of them are 64-bit values).

Also, for your equals() method, you can compare two double values with Double#compare().

Possible equals() and hashCode() for your example:

public boolean equals(Object other) {
    if (this == other) {
        return true;
    }
    if (null == other
            || this.getClass() != other.getClass()) {
        return false;
    }

    return Double.compare(this.dnum1, ((Test) other).dnum1) == 0;
}

public int hashCode() {
    long bits = Double.doubleToLongBits(this.dnum1);
    return (int) (bits ^ (bits >>> 32));
}

About floating point arithmetic

Your example shows the disadvantage of double usage for floating point calculations - even values with the same magnitude can give close, but different results. Maybe you should use BigDecimal?

Also, see this question answers.

Anatoly Shamov
  • 2,608
  • 1
  • 17
  • 27
  • 2
    Unfortunately, Double.compare( .9 - .1, .7 + .1 ) ) does not test equal; 1 is returned. And doubleToLongBits returns different values for the two expressions. – Jack Straub Oct 05 '17 at 18:22
  • 1
    That's right, because `(.9 - .1) = 0.8` and `(.7 + .1) = 0.7999999999999999`. And they are not equal, obviously. I think you're trying to solve this problem in a wrong way - maybe you should use [BigDecimal](http://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html), if you need a floating point arithmetic? And, of course, you can later convert it to `double`, if you need, with `doubleValue()`. – Anatoly Shamov Oct 05 '17 at 19:27
  • Thank you. BigDecimal would be suitable way to resolve the issue. – Jack Straub Oct 06 '17 at 04:32
  • 1
    If I'm writing a graphics application I would not want to use BigDecimal and I would still want to treat position (1.0, 1.0) as equal to position (.999999999999999, .99999999999999) – Jack Straub Jul 10 '20 at 01:03
0

How about this?

public static boolean equals( double param1, double param2 )
{
    final double    epsilon  = 1e-10;
    
    // accounts for 0, NaN, +/-INFINITY
    boolean result  = param1 == param2;
    if ( !result )
    {
        double quot = param1 / param2;
        result = quot > 0 && (1 - quot) < epsilon;
    }
    
    return result;
}

To demonstrate:

public static void main(String[] args)
{
    double dvar1 = 0.7 + 0.1;
    double dvar2 = 0.9 - 0.1;
    System.out.println( dvar1 == dvar2 ); // expect false
    System.out.println( equals( dvar1, dvar2 ) ); // expect true
}
Jack Straub
  • 452
  • 3
  • 13
0
@Override
public int hashCode() {
    return Double.hashCode(Math.round(dnum1));
}


public boolean equals( Object other )
{
    if(other instanceof Test)
        return equals(test);
    return false;
}
public boolean equals( Test other )
{
    if(other == null)
        return false;
    return isEqual(dnum1,other.dnum1);
}
public boolean isEqual(double number,double anotherNumber){
    if (number == anotherNumber)// eg 0, NaN, +/-INFINITY
        return true;
    return Math.abs(number - anotherNumber) <= EPSILON;
}
Islam Assem
  • 1,376
  • 13
  • 21