覆盖Equals和GetHashcode并进行双重比较

时间:2012-09-25 10:23:48

标签: c#-4.0 comparison double equals gethashcode

我遇到了相等的问题并将对象添加到字典

class DoublePoint
{
    public double X;
    public double Y;
    public double Z;

    public DoublePoint(double x, double y, double z)
    {
        this.X = x; this.Y = y; this.Z = z;
    }

    public override bool Equals(object obj)
    {
        try
        {
            DoublePoint dPoint = obj as DoublePoint;
            return this.X.IsEqualTo(dPoint.X) && this.Y.IsEqualTo(dPoint.Y) && this.Z.IsEqualTo(dPoint.Z);
        }
        catch
        {
            throw;
        }
    }

    public override int GetHashCode()
    {
        return this.X.GetCode() ^ this.Y.GetCode() ^ this.Z.GetCode();
    } 
}

static class extensions
{
    static double Tolerance = 0.001;
    public static bool IsEqualTo(this double d1, double d2)
    {
        return (d1 - d2) <= Tolerance;
    }

    public static int GetCode(this double d1)
    {
        byte[] data = BitConverter.GetBytes(d1);
        int x = BitConverter.ToInt32(data, 0);
        int y = BitConverter.ToInt32(data, 4);
        return x ^ y; 
    }
}

这是我的测试:

DoublePoint d1 = new DoublePoint(1.200, 2.3, 3.4);
        DoublePoint d2 = new DoublePoint(1.2001, 2.3, 3.4);
        DoublePoint d3 = new DoublePoint(1.200, 2.3, 3.4);
        bool isEqual = d1.Equals(d2); // true here


        Dictionary<DoublePoint, int> dict = new Dictionary<DoublePoint, int>();
        dict.Add(d1, 1); 
        dict.Add(d2, 2); // successful, d2 is also added but d2 is equal to d1
        dict.Add(d3, 3); // Error! since we have d1 already in dictionary

有了这个,

  1. 当我添加相同(具有一定容差)的双点对象时,我可以将它们添加到字典中。如何限制这些对象。

  2. 是比较具有一定容差的双数据类型的正确方法。

  3. 请指教。

    谢谢

1 个答案:

答案 0 :(得分:1)

将“相等”定义为“足够接近”存在问题。毫无疑问,这对于计算是有用的,但是这种“相等”违反了及物性规则:Equals如果a.Equals(b) && b.Equals(c),那么a.Equals(c) 必须保持为真(这是显然不是你代码的属性。)

因此,IsEqualTo不适合重新定义Equals

解决问题的可能方法有哪些? Equals必须分成不相交的“等价”值组。我通常会执行以下操作:定义规则以从组中获取“规范”值,因此如果规范组代表相等,则两个值“相等”。

简单示例:仅使用双精度值d,我们将规范值定义为Math.Floor(d)。所以这种方式你有1.0等于1.1,0.9等于0.0但等于1.0。这种方式不是理想的方法(毕竟,0.9不等于1.0但等于0.0似乎是错误的),但至少保留了及物性规则。

特别针对您的情况,可能就是这样:

class DoublePoint
{
    public double X;
    public double Y;
    public double Z;

    const double epsilon;
    void GetCanonicalValues(out double x, out double y, out double z)
    {
        x = Math.Floor(X / epsilon) * epsilon;
        y = Math.Floor(Y / epsilon) * epsilon;
        z = Math.Floor(Z / epsilon) * epsilon;
    }

    public override bool Equals(object obj)
    {
        DoublePoint that = obj as DoublePoint;
        if (that == null)
            return false;
        double x1, y1, z1, x2, y2, z2;
        this.GetCanonicalValues(out x1, out x2, out z2);
        that.GetCanonicalValues(out x1, out x2, out z2);
        return (x1 == x2) && (y1 == y2) && (z1 == z2); // here we can compare
    }

    ...

您的代码的另一个问题是,您的GetHashCode未与Equals对齐:如果a.Equals(b)a.GetHashCode() 必须等于{{1} }}

您也可以使用规范值解决此问题:

b.GetHashCode()

请注意 public override int GetHashCode() { double x, y, z; GetCanonicalValues(out x, out y, out z); return x.GetHashCode() ^ y.GetHashCode() ^ z.GetCode(); } } 的行为可能对您的需求不可接受 - 然后您需要以其他方式确保传递性。