如何用近似相等实现浮点哈希


15

假设我们有以下Python类(Java中存在的问题与equals和相同hashCode

class Temperature:
    def __init__(self, degrees):
        self.degrees = degrees

degrees开尔文的温度在哪里?现在,我想实现平等的测试和哈希为Temperature的方式,

  • 比较浮点数到ε差,而不是直接相等测试,
  • 并履行a == b隐含的合同hash(a) == hash(b)
def __eq__(self, other):
    return abs(self.degrees - other.degrees) < EPSILON

def __hash__(self):
    return # What goes here?

Python文档讨论了一些关于确保数字哈希hash(2) == hash(2.0)的问题,但这并不是完全相同的问题。

我是否在正确的轨道上?如果是这样,在这种情况下实现散列的标准方法是什么?

更新:现在我知道这种浮点数相等性测试消除了==和的可传递性equals。但是,如何将其与浮动的“常识”一起使用,不应该直接进行比较?如果通过比较浮点数实现相等运算符,则静态分析工具会抱怨。他们这样做正确吗?


9
为什么这个问题带有Java标签?
Laiv

8
关于您的更新:我会说散列浮点数通常是一个有问题的事情。尽量避免将float用作键或set元素。
J. Fabian Meier

6
@Neil:同时,四舍五入的声音不是整数吗?我的意思是:如果您可以四舍五入到千分之一度,那么您可以简单地使用定点表示法-整数,以千分之一度表示温度。为了易于使用,如果希望...,您可以让吸气剂/装填器透明地从/转换为浮子...
Matthieu

4
开氏温度不再是度数。学位也模棱两可。为什么不只是打电话呢kelvin
所罗门·乌科

5
Python 或多或少具有出色的定点支持,也许这对您来说是对的。
乔纳斯·谢弗

Answers:


41

实施温度的均等测试和散列,而不是直接进行均等测试,而是比较浮点数与ε差值,

模糊相等违反了Java对equals方法的要求,即可传递性,即if x == yy == zthen x == z。但是,如果您使用0.1的epsilon进行模糊等式,则0.1 == 0.2and 0.2 == 0.3,但0.1 == 0.3不成立。

尽管Python没有记录这样的要求,但是拥有非传递性相等的含义仍然使它成为一个非常糟糕的主意。对此类类型的推理会引起头痛。

因此,我强烈建议您不要这样做。

要么提供精确的相等性,然后以明显的方式在其基础上进行哈希处理,要么提供一种单独的方法进行模糊匹配,要么采用Kain建议的等效类方法。尽管在后一种情况下,我还是建议您将值固定为构造函数中等效类的代表成员,然后再使用简单的精确相等和哈希进行其余操作;这样就更容易推断类型了。

(但是,如果这样做,则不妨使用定点表示形式而不是浮点形式,即使用整数来计算千分之一度或所需的任何精度。)


2
有趣的想法。因此,通过累积数百万个ε并具有传递性,您可以得出结论:任何事物都等于其他事物:-)但是,这种数学约束是否承认浮点的离散基础?在许多情况下,浮点是它们打算表示的数量的近似值?
Christophe

@Christophe有趣的问题。如果您考虑一下,您会发现此方法将从分辨率大于epsilon(当然,其中心为0)的浮点数中创建一个大型等效类,并将其他浮点数各自留在自己的类中。但这不是重点,真正的问题是,是否得出两个数字相等的结论取决于是否比较了第三个数字以及处理的顺序。
Ordous

在处理@OP的编辑时,我要补充一点,浮点数的不正确==应该“感染” ==包含它们的类型。也就是说,如果他们遵循您的建议提供精确的相等性,那么他们的静态分析工具应进一步配置为在上使用相等性时发出警告Temperature。确实,这是您唯一可以做的事情。
HTNW

@HTNW:那太简单了。比率类可能具有float approximation不参与的字段==。此外,==当要比较的成员之一是类型时,静态分析工具将在类的实现内发出警告float
MSalters

@MSalters吗?据推测,足够可配置的静态分析工具可以完成我建议的功能。如果某个班级的某个float字段不参与==,则不要配置您的工具以==对该班级发出警告。如果该类确实存在,那么大概将其标记==为“太精确”将导致该工具忽略实现中的此类错误。例如在Java中,如果@Deprecated void foo()void bar() { foo(); }则为警告,但@Deprecated void bar() { foo(); }不是。也许许多工具不支持此功能,但有些工具可能不支持。
HTNW

16

祝好运

如果您不对哈希表感到愚蠢或牺牲epsilon,那么您将无法实现这一目标。

例:

假定每个点散列为其自己唯一的哈希值。

由于浮点数是连续的,因此在给定浮点值之前最多可以有k个数字,在给定浮点值之后最多可以有k个数字,它们都在给定点的一定范围内。

  1. 对于彼此之间不共享相同哈希值的epsilon中的每两个点。

    • 调整散列方案,以使这两点散列为相同的值。
  2. 对于所有这样的对,整个浮点数序列将朝单个具有值的方向崩溃。

在某些情况下,这并不成立:

  • 正/负无穷大
  • N
  • 对于给定的epsilon,一些未归一化的范围可能无法链接到主范围。
  • 也许其他一些特定格式的实例

但是,对于任何包含至少一个高于或低于某个给定浮点值的浮点值的epsilon值,> = 99%的浮点范围将散列为单个值。

结果

> = 99%的整个浮点范围散列到单个值会严重损害哈希值的意图(以及任何依赖于公平分布的低冲突哈希的设备/容器)。

或者,epsilon只允许精确匹配。

粒状

您当然可以选择更精细的方法。

使用这种方法,您可以定义精确的存储桶,直到特定的分辨率。即:

[0.001, 0.002)
[0.002, 0.003)
[0.003, 0.004)
...
[122.999, 123.000)
...

每个存储桶都有一个唯一的哈希,并且存储桶中的任何浮点都等于同一存储桶中的任何其他浮点。

不幸的是,仍然有可能两个浮子之间的距离为ε,并且有两个单独的哈希。


2
我同意,如果符合OP的要求,此处的细化方法可能是最好的。尽管我怕OP的类型要求大约为+/- 0.1%,但这意味着它不可能是粒度。
尼尔,

4
@DocBrown“不可能”部分是正确的。如果基于epsilon的相等性意味着哈希码相等,那么您将自动使所有哈希码相等,因此哈希函数不再有用。存储桶方法可能会富有成果,但是您将拥有带有彼此任意靠近的不同哈希码的数字。
J. Fabian Meier

2
可以通过不仅检查具有确切哈希键的存储桶,还检查两个相邻存储桶(或其中至少一个)的内容来修改存储桶方法。这样就消除了那些极端情况下的问题,即将运行时间最多增加了两倍(如果正确实施)。但是,它不会更改常规运行时间顺序。
布朗

当您的精神正确时,并非一切都会崩溃。使用固定的小ε,大多数数字只会相等。当然,对于那些epsilon来说将是无用的,因此,从精神上来说,您是正确的。
卡斯滕S

1
@CarstenS是的,我的陈述是99%的范围散列到单个哈希值实际上并没有覆盖整个float范围。有许多高范围值,它们之间的距离超过了epsilon,它们会散列到自己的唯一存储桶中。
Kain0_0

7

您可以在引擎盖下将温度建模为整数。温度具有自然下限(-273.15摄氏度)。因此,请加倍(对于您的基础整数,-273.15等于0)。您需要的第二个元素是映射的粒度。您已经在隐式使用此粒度。这是你的EPSILON。

只需将温度除以EPSILON并取其底,现在您的哈希值和相等值将同步运行。在Python 3中,整数是无界的,如果愿意,EPSILON可以更小。

当心 如果更改EPSILON的价值,你已经序列化对象,他们将不兼容!

#Pseudo code
class Temperature:
    def __init__(self, degrees):
        #CHECK INVALID VALUES HERE
        #TRANSFORM TO KELVIN HERE
        self.degrees = Math.floor(kelvin/EPSILON)

1

实现一个浮点哈希表可以找到与给定键“近似相等”的事物,将需要使用几种方法或其组合:

  1. 将每个值四舍五入为一个比“模糊”范围大的增量,然后再将其存储在哈希表中,并且在尝试查找值时,请检查哈希表中是否在所寻找的值之上和之下的舍入值。

  2. 使用要查找的值上方和下方的键将每个项目存储在哈希表中。

请注意,使用任何一种方法都可能需要哈希表条目不标识项目,而是标识列表,因为每个键可能有多个项目。上面的第一种方法将最小化所需的哈希表大小,但是每次搜索不在该表中的项目都将需要两次哈希表查找。第二种方法将能够迅速识别出表中没有项目,但通常将要求该表容纳大约两倍于其他项的条目。如果尝试在2D空间中查找对象,则可能对X方向使用一种方法,对Y方向使用一种方法,这样一来,不必将每个项目存储一次,而是需要对每个查找进行四个查询操作,或者能够使用一次查找来找到一个项目,但必须将每个项目存储四次,


0

当然,您可以通过删除尾数的最后八位,然后进行比较或散列来定义“几乎相等”。问题在于彼此之间非常接近的数字可能不同。

这里有些混乱:如果两个浮点数比较相等,则它们相等。要检查它们是否相等,请使用“ ==”。有时您不想检查是否相等,但是当您这样做时,“ ==”是可行的方法。


0

这不是答案,而是扩展注释可能会有所帮助。

我在使用时一直在解决类似的问题 MPFR(基于GNU MP)时。@ Kain0_0概述的“存储桶”方法似乎给出了可接受的结果,但是请注意该答案中突出显示的限制。

我想补充一说-根据您要执行的操作-使用像Mathematica 这样的“精确”(警告购买者)计算机代数系统可以帮助补充或验证不精确的数字程序。这将使您无需担心舍入就可以计算结果,例如,7*√2 - 5*√2将屈服2而不是2.00000001或类似。当然,这会带来其他可能不值得的并发症。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.