众所周知,通过==比较浮点数通常是一个错误.在我写的3D矢量类(具有浮点分量X,Y,Z)中,如果它们的距离被认为是零,则认为两个矢量相等.
public override bool Equals(object obj) { if (obj == null) { return false; } if (GetType () != obj.GetType ()) { return false; } float d = DistSq ((Vec) obj); return IsConsideredZero (d); } public float DistSq(Vec p) { Vec d = this - p; return d.LengthSq (); } public float LengthSq() { return X * X + Y * Y + Z * Z; } private const float VEC_COMPARE_EPSILON_ABS = 1E-05f; public static bool IsConsideredZero(float f) { return Math.Abs (f) < VEC_COMPARE_EPSILON_ABS; }
到目前为止,一切正常.但是,现在我想获得向量的哈希码.我可以看到类似的东西hash = (int)X^(int)Y^(int)Z
一定会失败.
我能想到的最好的是:
public override int GetHashCode() { return 0; }
当然,这有点糟透了.有没有办法获得合理的哈希码?NaNs和其他特殊值是可能的,但不太可能,如果这很重要的话.
假设您想要具有正常的哈希码/相等属性,这是不可能的:
如果X = Y且Y = Z则X = Z(传递性)
如果X = Y则Y = X(传播)
X = X表示所有X(反身性)
第一个规则是问题 - 因为如果每个值被认为与下一个更大的可表示数字"相等",那么最终所有数字都相等.例如,假设一个数字被认为等于另一个数字,它们在0.1以内:
0等于0.08 0.08等于0.16 0.16等于0.24
传递规则=> 0等于0.16 =传递规则=> 0等于0.24
(等等)
如果忽略传递性规则,那么您(可能)仍希望"相等"值具有相同的哈希码.这有效地强制执行传递规则 - 在上面的例子中,0和0.08必须具有相同的哈希码,0和0.16也是如此.因此0和0.16必须具有相同的哈希码,依此类推.因此,您可以没有有用的哈希码 - 它必须是常量.
我不认为你可以有一个与你的比较方法一致的哈希码,因为后者不是传递性的:对于任何三个向量A,B,C,if A.Equals(B)
和B.Equals(C)
为真,它仍然可能A.Equals(C)
是假的.(想象一下,如果A和B之间的距离是6e-6,B和C之间的距离是6e-6,A和C之间的距离是1.2e-5)但是哈希码的相等总是可传递的,因为它们只是数字.
在这种情况下,我只是创建一个哈希码方法,根据浮点坐标的精确值计算哈希值,并在文档中提到它与equals不一致.我知道这不是一个真正的解决方案,但考虑到我认为不存在真正的解决方案,最好有一个非平凡的哈希码,而不仅仅是0.
我担心这不是一般情况.证明的草图如下:
取两个数字a和b.让他们之间的区别是d.然后,如果您创建d/epsilon数字,其间有一个epsilon步骤,则每个步骤必须与之前的步骤"相等",这通过哈希码语义具有相同的哈希码.所以所有数字必须具有相同的哈希码.
如果添加其他约束,则只能解决此问题.
顺便说一句,你对Equals的定义也是错误的,因为a.Equals(b)和b.Equals(c)可能是真的,而不是a.Equals(c),这对于equals是错误的.这被称为打破Transitivity属性.
解决方案取决于您使用哈希的内容.一种解决方案是引入概念网格.如果在同一个网格立方体中,通过舍入到常数小数位,然后在舍入数字上取等号和哈希码,更改等号和哈希码使两个数字相等.如果接近零是一个重要的情况,在舍入之前添加epsilon/2的偏移量,因此零是立方体的中心.这是正确的,但你可以任意地将两个数字放在一起(在浮点数的限制下)而不是相等的.因此,对于某些应用程序,它将是正常的,而其他应用程序则不会.这类似于mghie的想法.