有没有办法用epsilon获取浮点数的哈希码?

时间:2009-02-24 08:20:52

标签: floating-point hashcode

众所周知,通过==比较浮点数通常是一个错误。在我写的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和其他特殊值是可能的,但不太可能,如果这很重要的话。

5 个答案:

答案 0 :(得分:13)

假设您希望拥有正常的哈希码/相等属性,那是不可能的:

  • 如果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

=&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。所有相等的点将在两个哈希值之一上进行比较相等。这将要求您在哈希表中输入两次点,每个哈希例程一次。