Floating point 有没有办法用epsilon获得浮点的哈希代码?

Floating point 有没有办法用epsilon获得浮点的哈希代码?,floating-point,hashcode,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

众所周知,通过
==
比较浮点通常是错误的。在我写的一个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;
}

当然,这有点糟糕。有没有办法得到一个合理的哈希代码?NaN和其他特殊值是可能的,但不太可能,如果这很重要的话。

假设您想要具有正常的hashcode/相等属性,则不可能:

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

(等)


如果忽略传递性规则,那么您仍然(大概)希望“equal”值具有相等的哈希代码。这有效地执行了传递性规则——在上面的示例中,0和0.08必须具有相等的哈希代码,0和0.16也是如此。因此0和0.16必须具有相等的哈希码,以此类推。因此,您可能没有有用的hashcode—它必须是一个常量。

恐怕在一般情况下不是这样。证据的草图如下所示:

取任意两个数字a和b。让他们之间的区别是d。然后,如果您创建的d/epsilon数中间有一个epsilon步长,那么每个步长都必须与前面的步长“相等”,根据hashcode语义,该步长具有相同的hashcode。因此,所有数字必须具有相同的哈希代码

只有添加其他约束才能解决此问题

另一方面,你对平等的定义也是错误的,因为a.Equals(b)和b.Equals(c)可能是真的,但a.Equals(c)却不是,这对平等是错误的。这被称为破坏传递性

那我该怎么办?
解决方案取决于您使用哈希的目的。一种解决方案是引入概念网格。更改equals和hashcode,使同一网格多维数据集中的两个数字相等,方法是四舍五入到小数位数不变,然后对四舍五入的数字使用equals和hashcode。如果接近零是一个重要的情况,则在取整之前添加epsilon/2的偏移量,因此零是立方体的中心。这是正确的,但您可以让两个数字任意接近(在float限制下),而不会相等。因此,对于某些应用程序,它是可以的,而对于其他应用程序,它不是。这类似于。

我认为你不可能有一个与你的比较方法一致的哈希代码,因为后者是不可传递的:对于任何三个向量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),但是哈希代码的相等性总是可以传递的,因为它们只是数字


在本例中,我将创建一个hashcode方法,该方法根据浮点坐标的精确值计算哈希值,并在文档中提到它与equals不一致。我知道这不是一个真正的解决方案,但考虑到我认为不存在真正的解决方案,最好有一个非平凡的哈希代码,而不仅仅是0。

每个人都是正确的

然而,经常做的一件事是稍微扩展散列的概念。考虑你的3D空间的分区,并带有一个边>ε的盒子。

点的散列就是它所属的框。
当您想要查找点时,您不会使用相应的框来检查该点(就像常规散列一样),而是检查相邻的框。在3d中,你最多可以使用8个盒子。

任何你使用的技术都会有问题,因为你提出了一些不可能解决的问题

您需要的是1)均匀分布的散列,以便对于大多数数字a和b,其中a!=b然后是a.GetHashCode()!=b、 GetHashCode()但2)其中a==b,则a.GetHashCode()==b.GetHashCode()必须为真

返回一个常量满足(2),但不满足(1)

您可以演示1E-5边界处的舍入并将其用作哈希违反(1),但违反(2)。以1E-5和2E-5为例。舍入将产生两个不同的散列值,但它们比较相等。这违反了上述约束(2)。您可以很容易地对此进行概括,以证明数字的任何舍入都会遇到类似的问题


我建议你选择一种不同的方法。我假设潜在的问题是确定某个点是否接近您已经拥有的点。我建议将坐标空间重复划分为两半(其中沿边界的点(即,我知道这是不相关的,但如果向量非常大,则计算两个向量之间的距离以查看它们是否相等将不起作用)。始终最好计算相对距离:最好执行(d2-d1)/(d1+d2)大卫:是的,但我认为你的想法对于非常小的向量(接近零)是失败的,这是我的程序中更重要的极端情况。请注意未来的读者:这可能会对你的回答有帮助:+1,并感谢你告诉我我的错误。那么我们如何实现与标准Java容器的近似匹配呢?在我的情况下,我很幸运,因为我实际上不需要epsilon,但还有什么替代方法?可以吗例如,我们仍然可以让HashMap在没有运行的hashcode的情况下工作?@Thomas:你总是可以为所有值返回一个常量hashcode——但是如果你违反了平等契约,你会得到明显的奇怪行为。
public override int GetHashCode()
{
    return 0;
}