【问题标题】:Hash function for floats浮点数的散列函数
【发布时间】:2011-05-13 09:27:10
【问题描述】:

我目前正在用 C++ 实现一个哈希表,我正在尝试为浮点数创建一个哈希函数...

我打算通过填充十进制数将浮点数视为整数,但后来我意识到我可能会遇到大数字溢出...

有散列浮点数的好方法吗?

你不必直接给我函数,但我想看看/理解不同的概念......

注意事项:

  1. 我不需要它真的很快,只要可能的话均匀分布。

  2. 我读到由于计算速度的原因,不应该对浮点数进行散列,有人可以确认/解释这一点并给我其他为什么不应该散列浮点数的原因吗?我真的不明白为什么(除了速度)

【问题讨论】:

    标签: c++ floating-point hashtable hash-function


    【解决方案1】:

    这取决于应用程序,但大多数时候不应该对浮点数进行散列,因为散列用于快速查找精确匹配,并且大多数浮点数是产生浮点数的计算结果,该浮点数只是正确答案的近似值。检查浮动相等性的通常方法是检查它是否在正确答案的某个增量(绝对值)内。这种类型的检查不适合散列查找表。

    编辑

    通常,由于舍入误差和浮点运算的固有限制,如果您希望浮点数 ab 应该彼此相等,因为数学上是这样,您需要选择一些 相对较小的delta > 0,然后如果abs(a-b) < delta,则声明ab 相等,其中abs 是绝对值函数。详情请见this article

    这是一个演示问题的小例子:

    float x = 1.0f;
    x = x / 41;
    x = x * 41;
    if (x != 1.0f)
    {
        std::cout << "ooops...\n";
    }
    

    根据您的平台、编译器和优化级别,这可能会将ooops... 打印到您的屏幕上,这意味着数学方程式x / y * y = x 不一定适用于您的计算机。

    在某些情况下,浮点运算会产生精确的结果,例如分母为 2 次方的合理大小的整数和有理数。

    【讨论】:

    • 您能再解释一下吗? “检查浮动相等性的通常方法是检查它是否在正确答案的某个增量(绝对值)内。”
    • @Leo Davidson 我知道我会遇到麻烦,这个练习的目标是找到准确的时间;-)
    • 投反对票,因为它没有回答问题。我在这里是因为我需要不精确的哈希。关于危险的建议很好,但回答问题更好。
    • 简单地回答提问者的问题通常是对提问者的伤害。我是根据这个论坛的经验发言的。要求散列浮点数的人可能走错了路,特别是考虑到所提出的问题。如果你想问一个关于模糊查找、浮点数的等价类等问题,那就是另一个问题了。
    • 所提出的问题通常是错误的方法并不意味着它总是。有时程序员需要做一些事情不是因为它是一个好主意,但是,有外部因素迫使它去做(例如,一个固执的老板,短视的要求等)。或者甚至可能在某些不寻常的情况下确实有意义。按原样回答问题是一种伤害,但 Stack Overflow 的部分目的是为未来的 Google 搜索者提供知识库。不应完全忽略实际问题。
    【解决方案2】:

    如果您的哈希函数执行以下操作,您会在哈希查找中获得某种程度的模糊性

    unsigned int Hash( float f )
    {
        unsigned int ui;
        memcpy( &ui, &f, sizeof( float ) );
        return ui & 0xfffff000;
    }
    

    通过这种方式,您将屏蔽 12 个最低有效位,从而产生一定程度的不确定性......不过,这实际上取决于您的应用程序。

    【讨论】:

    • 不,0xfffff000 屏蔽了 3 个半字节,即 12 位。可能有点太多了。如果要屏蔽 3 位,请使用 0xfffffff8
    • @FredOverflow:不.. 你是对的.. 我不是说 3 ......改变了
    • @Goz:这取决于目标机器上float 的内部表示,因为您在这里假设尾数位于最低有效位,并且以小端方式存储.虽然模糊的想法绝对是要走的路。
    • 你仍然会得到一对相对差异很小的数字,它们最终会被放入不同的 bin 中。
    • @Ben:你当然会。如果您要分桶,而哈希算法必然会这样做,那么您将始终遇到此问题。想象每 0.1 处的桶到 0.05 两边。这意味着 1.4999999 进入 1 个桶,1.5 进入另一个桶。你只需要忍受它或放弃任何形式的桶......
    【解决方案3】:

    可以使用std hash,还不错:

     std::size_t myHash = std::cout << std::hash<float>{}(myFloat);
    

    【讨论】:

      【解决方案4】:
      unsigned hash(float x)
      {
          union
          {
              float f;
              unsigned u;
          };
          f = x;
          return u;
      }
      

      技术上未定义的行为,但大多数编译器都支持这一点。替代解决方案:

      unsigned hash(float x)
      {
          return (unsigned&)x;
      }
      

      这两种解决方案都取决于您机器的字节序,例如在 x86 和 SPARC 上,它们会产生不同的结果。如果这不打扰您,只需使用其中一种解决方案即可。

      【讨论】:

      • 不是有一些标准函数可以用来抓取尾数和指数吗?我不是喜欢浮动的人,也不是 C++ 的人,所以我只是想知道......
      • @GregS:据我所知没有。无论如何,您为什么要获取尾数和指数?浮点数是 32 位的,为什么不简单地将其解释为无符号呢?只要你避免使用 NaN,你应该没问题...
      • @FredOverflow:我只是猜测分别获取尾数和指数会产生更少的机器和编译器相关结果。我仍然会依赖尾数和指数的大小,这可能与编译器和机器相关。
      • @Pacane:你指的是变量u吗? union hack 是基于fu 共享相同内存的假设,因此u 通过写入f 被“初始化”。是的,这是高度特定于实现的,但它通常有效。第二种方式是参考转换。它引入了另一种访问对象x(float 类型)的方法,就好像它是无符号类型的对象一样。
      • @Pacane:你说的“pad”是什么意思?如果您是这样认为的,那么无论如何都不会发生价值转换。例如,hash(3.14f) 不会产生 3,而是 1078523331,因为这两个值都由机器字 0x4048f5c3 表示。当然,这假设 int 和 float 都是 32 位类型,这是高度特定于实现的等。(您可以将引用转换基本上视为 *(unsigned*)&amp;x 的简写。)
      【解决方案5】:

      您当然可以将float 表示为相同大小的int 类型来对其进行哈希处理,但是这种幼稚的方法有一些您需要小心的陷阱...

      简单地转换为二进制表示容易出错,因为相等的值不一定具有相同的二进制表示。

      一个明显的例子:例如-0.0 不会匹配0.0*

      此外,简单地转换为相同大小的int 不会产生非常均匀的分布,这通常很重要(例如,实现使用桶的哈希/集)。

      建议的实施步骤:

      • 过滤掉非有限情况(naninf)和(0.0-0.0是否需要显式执行此操作取决于使用的方法)。
      • 转换为相同大小的int
        (例如,使用联合将float 表示为int,而不是简单地转换为int).
      • 重新分配位,(这里故意模糊!),这基本上是速度与质量的权衡。但是,如果您在一个小范围内有很多值,您可能不希望它们也处于相似范围内。

      *:您可能也不想检查 (nan-nan)。 如何处理这些完全取决于您的用例(您可能希望像 CPython 一样忽略所有 nan 的符号)。

      Python 的 _Py_HashDouble 是在生产代码中如何散列 float 的一个很好的参考(忽略最后的 -1 检查,因为这是 Python 的特殊值) .

      【讨论】:

      • “例如 -0.0 不会匹配 0.0”的明显情况是 唯一 一对浮点值的示例,它们对于 == 是相等的并且具有不同表示,所以我不确定你为什么要概括它。无穷大当然不需要被过滤掉。有些人(认真地)建议为hash(NaN) 返回一个随机整数,但将NaN 用作哈希表中的键似乎更合理:research.swtch.com/randhash
      • PS:我链接的博文发布于 4 月 1 日。我没有意识到这一点,因为我是从档案中读到的。这可能并不严重,但同时,hash(NaN) 的随机结果意味着以 NaN 为键的绑定存在于哈希表中并且可以迭代,因此对于某些人来说它实际上是一个很好的解决方案用例。
      • @Pascal Cuoq - 你如何处理!finite 值取决于你自己的实现,我只是说你应该在散列浮点数时注意它们,并将浮点数转换为其他答案中建议的 int 忽略了很多。回复:-0 vs 0 - 有 -nan / nan 但如何分类这些可能取决于您自己的偏好(您可能想像 Python 一样忽略 nan 的符号)。更新了答案。
      • 注意,强烈建议不要从nan 返回随机整数。有人认为这是一个笑话是有原因的。保持哈希函数的确定性,或者使用某种错误更多的是实现细节。
      • 对我来说这似乎不是个玩笑。 “确定性”意味着x == y 意味着hash(x) == hash(y),即使hash(NaN) 被定义为随机,NaNNaN 仍然如此。为什么希望及时返回相同值的原因比对“确定性”的一些误解要强得多,并在博客文章中进行了解释。
      【解决方案6】:

      如果你有兴趣,我刚刚做了一个使用浮点并且可以散列浮点数的散列函数。它还通过了SMHasher(这是非加密哈希函数的主要偏差测试)。由于浮点计算,它比普通的非加密哈希函数慢很多。

      我不确定tifuhash 是否会对所有应用程序都有用,但有趣的是看到一个简单的浮点函数同时传递了 PractRand 和 SMHasher。

      主要的状态更新函数很简单,看起来像:

      function q( state, val, numerator, denominator ) {
        // Continued Fraction mixed with Egyptian fraction "Continued Egyptian Fraction"
        // with denominator = val + pos / state[1]
        state[0] += numerator / denominator;
        state[0] = 1.0 / state[0];
      
        // Standard Continued Fraction with a_i = val, b_i = (a_i-1) + i + 1
        state[1] += val;
        state[1] = numerator / state[1];
      }
      

      不管怎样,你可以get it on npm 或者你可以check out the github

      使用很简单:

      const tifu = require('tifuhash');
      
      const message = 'The medium is the message.';
      const number = 333333333;
      const float = Math.PI;
      
      console.log( tifu.hash( message ), 
        tifu.hash( number ),
        tifu.hash( float ),
      tifu.hash( ) );
      

      这里有一个关于 runkit 的一些哈希的演示 https://runkit.com/593a239c56ebfd0012d15fc9/593e4d7014d66100120ecdb9

      旁注:我认为在未来使用浮点,可能是大数组的浮点计算,可能是一种有用的方法,可以在未来产生更多计算要求的哈希函数。我发现使用浮点的一个奇怪的副作用是哈希是依赖于目标的,我猜也许它们可以用来识别计算它们的平台。

      【讨论】:

        【解决方案7】:

        由于 IEEE 字节排序,Java Float.hashCode() 和 Double.hashCode() 没有给出好的结果。这个问题是众所周知的,可以通过这个加扰器来解决:

        class HashScrambler {
        
            /**
             * https://sites.google.com/site/murmurhash/
             */
            static int murmur(int x) {
                x ^= x >> 13;
                x *= 0x5bd1e995;
                return x ^ (x >> 15);
            }
        
        }
        

        然后你会得到一个很好的哈希函数,它还允许你在哈希表中使用 Float 和 Double。但是您需要编写自己的哈希表,允许自定义哈希函数。

        由于在哈希表中您还需要测试相等性,因此您需要完全相等才能使其工作。也许后者是詹姆斯·K·波尔克总统想要解决的问题?

        【讨论】:

          猜你喜欢
          • 2019-11-29
          • 2011-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-23
          • 1970-01-01
          • 2010-12-19
          相关资源
          最近更新 更多