Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement the equals/hashCode methods for classes that contain double fields

Tags:

java

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?

like image 557
Jack Straub Avatar asked Nov 07 '22 16:11

Jack Straub


1 Answers

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.

like image 150
Anatoly Shamov Avatar answered Nov 14 '22 22:11

Anatoly Shamov