11

I'm implementing a reusable DoubleEqualityComparer (with a custom tolerance: the "epsilon" constructor parameter) to ease the usage of LINQ with sequences of double. For example:

bool myDoubleFound = doubles.Contains(myDouble, new DoubleEqualityComparer(epsilon: 0.01));

What is the right way to implement GetHashCode? Here's the code:

   public class DoubleEqualityComparer : IEqualityComparer<double>, IEqualityComparer<double?>
    {
        private readonly double epsilon;

        public DoubleEqualityComparer(double epsilon)
        {
            if (epsilon < 0)
            {
                throw new ArgumentException("epsilon can't be negative", "epsilon");
            }

            this.epsilon = epsilon;
        }

        public bool Equals(double x, double y)
        {
            return System.Math.Abs(x - y) < this.epsilon;
        }

        public int GetHashCode(double obj)
        {
            // ?
        }
   }

PS: I can always return the same value (ex: GetHashCode(double obj){ return 0; }) to always force the call to Equals(double, double) method (not very performant, I know), but I remember that this solution causes problems when the comparer is used with a dictionary...

Notoriousxl
  • 1,540
  • 1
  • 16
  • 27
  • 12
    You shouldn't do this because it violates transitivity. It's possible that `a equals b` and `b equals c` but `a not equals c`. – Ani Jun 29 '12 at 09:28
  • I have a similar issue, regarding Points in geometry, for all intents and purposes points are considered equal if they are "close enough" because of how doubles are stored and the tolerance is required, so if equal your hash code SHOULD generate an identical hash so when trying to use dictionaries to track and store identical points it falls apart due to a bad GetHashCode method. And there are all kinds of reasons a dictionary is useful so curious if you found a solution otherwise I will just write a custom dictionary class which is less performance to rely on equals not get hash code. – fr332lanc3 Nov 28 '20 at 19:44

2 Answers2

6

I'm not sure using IEqualityComparer<T> is the way to go. Because compared objects are not equals.

Maybe you should consider using a simple Any clause + an utility method :

private static bool DoublesAreNearlyEquals(double d1, double d2, double epsilon = 0.01D)
{
    return System.Math.Abs(d1 - d2) < this.epsilon;
}

private void foo()
{
    var myDoubles = Getdoubles();
    var doubleToSearch = 42D;
    var result = myDoubles.Any(d=>DoublesAreNearlyEquals(d, doubleToSearch));
}
malat
  • 12,152
  • 13
  • 89
  • 158
Steve B
  • 36,818
  • 21
  • 101
  • 174
  • 1
    Thanks, you and Ani convinced me not to use IEqualityComparer, but to define a custom interface (and its set of extension methods, LINQ-style): public interface ITolerable { bool AreAlmostEqual(T x, T y); } IEqualityComparer was handy because there are LINQ official methods ready to use (that don't call GetHashcode), but I undestand to leave it not implemented was awful (and dangerous). – Notoriousxl Jul 02 '12 at 12:33
  • @steve I do not agree. The IEqualityComparer (generics and not) interface was created exactly to allow a custom definition of what equality means to you. For example you can create a custom equality operator for double that compares the nearly value but uses the original double.GetHashCode for the hash generation. If you want to go deep, my advice is to take a look on how the StringComparer class is implemented on reference source. HTH – LoxLox Nov 04 '20 at 01:25
2

I would throw NotSupportedException in GetHashCode so you can have your cake and eat it too. This gives you the convenience of having an IEqualityComparer in LINQ and other methods, but guarantees that any usage of GetHashCode blows up. In practice, you might find that the way you use the equality comparer never actually requires GetHashCode to be called. You might even call this class NotHashableDoubleEqualityComparer to be super clear about the limitation to callers.

Suraj
  • 35,905
  • 47
  • 139
  • 250