众所周知,通过==比较浮点数通常是一个错误。在我写的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和其他特殊值是可能的,但不太可能,如果这很重要的话。
答案 0 :(得分:13)
假设您希望拥有正常的哈希码/相等属性,那是不可能的:
第一个规则是问题 - 因为如果每个值被认为与下一个更大的可表示数字“相等”,那么最终所有数字相等。例如,假设一个数字被认为等于另一个数字,它们在0.1以内:
0等于0.08 0.08等于0.16 0.16等于0.24
=&GT;传递规则0等于0.16 =&GT;传递规则<0>等于0.24
(等)
如果您忽略传递规则,那么您仍然(可能)希望“相等”值具有相同的哈希码。这有效地强制执行传递规则 - 在上面的例子中,0和0.08必须具有相同的哈希码,0和0.16也是如此。因此0和0.16必须具有相同的哈希码,依此类推。因此,您可以没有有用的哈希码 - 它必须是常量。
答案 1 :(得分:8)
我认为您不能拥有与您的比较方法一致的哈希码,因为后者不具有传递性:对于任何三个向量A,B,C,如果A.Equals(B)
和B.Equals(C)
是是的,可能仍然是A.Equals(C)
为假的情况。 (想象一下,如果A和B之间的距离是6e-6,B和C之间的距离是6e-6,A和C之间的距离是1.2e-5)但是哈希码的相等总是可传递的,因为它们只是数字。 / p>
在这种情况下,我只是创建一个哈希码方法,根据浮点坐标的精确值计算哈希值,并在文档中提到它与equals不一致。我知道这不是一个真正的解决方案,但考虑到我不认为存在真正的解决方案,最好有一个非平凡的哈希码,而不仅仅是0.
答案 2 :(得分:7)
我担心这不是一般情况。证明的草图如下:
取两个数字a和b。让他们之间的区别是d。然后,如果您创建d / epsilon数字,其间有一个epsilon步骤,则每个步骤必须与之前的步骤“相等”,这通过哈希码语义具有相同的哈希码。所以所有数字都必须具有相同的哈希码。
如果添加其他约束,则只能解决此问题。
顺便说一句,你对Equals的定义也是错误的,因为a.Equals(b)和b.Equals(c)可能是真的,而不是a.Equals(c),这对于equals是错误的。这被称为破坏传递性属性。
解决方案取决于您使用哈希的内容。一种解决方案是引入概念网格。如果在同一个网格立方体中,通过舍入到常数小数位,然后在舍入数字上取等号和哈希码,更改等号和哈希码使两个数字相等。如果接近零是一个重要的情况,在舍入之前添加epsilon / 2的偏移量,因此零是立方体的中心。这是正确的,但你可以任意地将两个数字放在一起(在浮点数的限制下)而不是相等的。因此,对于某些应用程序,它将是正常的,而其他应用程序则不会。这类似于mghie中的想法。
答案 3 :(得分:4)
每个人都是正确的......
然而,经常做的一件事是扩展哈希的概念。考虑您的3d空间的分区,其中框的边是&gt;&gt;小量。
点的哈希是它所属的框。 当您想要查找某个点时,不要检查具有相应框的点(就像您对常规哈希所做的那样),而是检查相邻的框。在3d中你最多可以获得8个盒子。
答案 4 :(得分:2)
无论你使用什么技术都会遇到问题,因为你提出了一些无法解决的问题。
你想要的是1)均匀分布的散列,这样对于大多数数字a和b,其中a!= b然后是a.GetHashCode()!= b.GetHashCode()但是2)其中a == b然后是a.GetHashCode ()== b.GetHashCode()必须为true。
返回常量满足(2)但不满足(1)。
您可以证明在1E-5边界处舍入并将其用作哈希违反了履行(1)但违反了(2)。以1E-5和2E-5为例。舍入将产生两个不同的哈希值,但它们相等。这违反了上面的约束(2)。您可以轻松地对此进行概括,以证明数字的任何四舍五入都会遇到类似的问题。
我建议你选择不同的方法。我假设潜在的问题是确定某个点是否接近你已经拥有的点。我建议将坐标空间重新划分为两半(沿着边界的点(即,从边界<= 1E-5)两半)。如果逐步划分空间(想想二叉树),您可以构建一个数据结构,该结构将快速返回您想要的结果并且相当容易构建。
如果我错过了我的猜测并且您必须使用哈希,那么可以使用两个哈希值执行您想要的操作,每个哈希值四舍五入到1E-5但偏移5E-6。所有相等的点将在两个哈希值之一上进行比较相等。这将要求您在哈希表中输入两次点,每个哈希例程一次。