【问题标题】:Hash 32bit int to 16bit int?将 32 位整数散列到 16 位整数?
【发布时间】:2010-06-17 00:32:17
【问题描述】:

有哪些简单的方法可以将 32 位整数(例如 IP 地址,例如 Unix time_t 等)散列到 16 位整数?

例如hash_32b_to_16b(0x12345678) 可能会返回 0xABCD

让我们从一个可怕但实用的示例解决方案开始:

function hash_32b_to_16b(val32b) {
    return val32b % 0xffff;
}

问题专门针对 JavaScript,但您可以随意添加任何与语言无关的解决方案,最好不要使用库函数。

此问题的上下文是生成唯一 ID(例如,一个 64 位 ID 可能由多个 32 位值的多个 16 位哈希组成)。避免碰撞很重要。

简单 = 好。古怪+混淆=有趣。

【问题讨论】:

  • XOR 高 2 字节与低 2 字节? 0x1234 异或 0x5678。但是你不能用“密码学”来标记这个问题并要求这样的东西......
  • @Remus:为什么我不能将它标记为“密码学”?这不是一个精炼且极其简单的加密相关问题吗?附言为什么不发表您的评论作为答案?
  • 同上一条评论一样,因为没有办法用16位数字表示32位数字的相同数量的唯一性,你不妨只取一半的数字。例如0x1234 或 0x5678。通过这种方式,至少对未来的代码维护者来说,唯一性的丧失是非常明显的。
  • 加密是一种可能的“好”哈希。这意味着明文和哈希之间存在一定程度的分离。这里的第一条评论没有那种质量(加密),但对于许多用途来说仍然是一个很好的哈希。
  • 以下页面有几种通用哈希函数的实现,它们高效且冲突最小:partow.net/programming/hashfunctions/index.html

标签: javascript hash integer


【解决方案1】:

最大化保留某些原始 32 位“信号”的熵的关键是确保 32 个输入位中的每一个都具有独立且相等的能力改变 16 位输出字的值。

由于 OP 要求的位大小恰好是原始的一半,因此满足此标准的最简单方法是 XOR 上半部分和下半部分,正如其他人所提到的。使用 XOR 是最优的,因为正如 XOR 的定义中的 obvious 一样,独立翻转 32 个输入位中的任何一个都可以保证改变 16-位输出。

当您需要进一步减小 一半大小(例如从 32 位输入 >2 位输出。请记住,目标是尽可能多地从源中保留熵,因此涉及用(i & 3) 天真地掩盖两个最低位的解决方案通常会朝着错误的方向前进;这样做保证没有任何位除了未屏蔽位影响结果,这通常意味着运行时信号中有一个任意的、可能有价值的部分是无原则地被直接抛弃。

从前面的段落开始,您当然可以再用 XOR 迭代 3 次,以产生一个 2 位输出,该输出具有所需的属性,即被每个 同等影响 /任何输入位。当然,该解决方案仍然是最佳正确的,但涉及循环或多个展开操作,事实证明,这不是必需的!

幸运的是,有一种很好的技术,只有 两个操作 可以提供 可证明的最佳结果这个情况。与 XOR 一样,它不仅确保对于任何给定的 32 位值,旋转任何单个输入位都会导致(例如)2 位输出值发生变化,而且也就是说,给定输入值的均匀分布,2 位输出值的分布也将完全均匀。例如,在4,294,967,296 可能的输入值上,该方法准确地给出四个可能的2 位哈希结果{ 0, 1, 2, 3 } 中的每一个的1,073,741,824

我在这里提到的方法使用了我通过详尽搜索发现的特定魔法值,并且在互联网上的其他地方似乎没有进行太多讨论,至少对于这里讨论的特定用途(即确保统一最大程度地保持熵的散列分布)。奇怪的是,根据同样的详尽搜索,魔法值实际上是唯一的,这意味着对于每个目标位宽 { 16, 8, 4, 2 },我在下面显示的魔法值是 only 值,当我在这里展示时,它满足上述完美的散列标准。

事不宜迟,将 32 位散列到 n = { 16, 8, 4, 2 } 的唯一且数学上最优的过程是 与对应于 n 的魔法值(无符号,丢弃溢出),然后取结果的n 最高位。要将这些结果位隔离为[0 ... (2ⁿ - 1)] 范围内的哈希值,只需将乘法结果右移(无符号!)32 - n 位即可。

“神奇”值和类C表达式语法如下:

最大限度地保留熵的哈希,用于从 32 位减少到...

目标位乘数右移表达式 ----------- ------------ ----------- ---------------- -------- 16 0x80008001 16 (i * 0x80008001) >> 16 8 0x80808081 24 (i * 0x80808081) >> 24 4 0x88888889 28 (i * 0x88888889) >> 28 2 0xAAAAAAAB 30 (i * 0xAAAAAAAB) >> 30


注意事项:

  1. 使用无符号 32 位乘法并丢弃任何溢出(不需要 64 位乘法)。
  2. 如果使用右移(如图所示)隔离结果,请务必使用 unsigned 移位操作。


[编辑:为 64 位输入值添加了表]

最大熵保留哈希,用于将 64 位值减少到...

目标位乘数右移表达式 ------------ ------ ----------- ---------- --------------------- 32 0x8000000080000001 32 (i * 0x8000000080000001) >> 32 16 0x8000800080008001 48 (i * 0x8000800080008001) >> 48 8 0x8080808080808081 56 (i * 0x8080808080808081) >> 56 4 0x8888888888888889 60 (i * 0x8888888888888889) >> 60 2 0xAAAAAAAAAAAAAAAAB 62 (i * 0xAAAAAAAAAAAAAAAAB) >> 62



进一步讨论

我发现这一切都很酷。实际上,关键信息理论要求是保证,对于任何m-bit 输入值及其对应的n-bit 哈希值结果,翻转m 源位中的任何一个总是会导致n-bit 结果值发生了一些变化。现在虽然总共有2ⁿ 个可能的结果值,其中一个已经“使用”(由结果本身),因为“切换”到那个从任何其他结果将根本没有变化。这使得2ⁿ - 1 结果值可以被整个m 输入值集使用一个位翻转。

让我们考虑一个例子;事实上,为了展示这种技术看起来是如何接近于怪异或彻头彻尾的魔法,我们将考虑更极端的情况,m = 64n = 2。对于 2 个输出位,有四个可能的结果值,{ 0, 1, 2, 3 }。假设一个任意的64位输入值0x7521d9318fbdf523,我们得到它的2位哈希值1

 (0x7521d9318fbdf523 * 0xAAAAAAAAAAAAAAAB) >> 62   // result -->  '1'

所以结果是 1 并且声称 没有值64 个值的集合 其中0x7521d9318fbdf523 的一位被切换可能具有相同的结果值。也就是说,这 64 个其他结果中没有一个可以使用值1,而所有结果都必须使用023。所以在这个例子中,除了 64 个其他输入值之外,似乎 2⁶⁴ 输入值中的每一个都会自私地为自己占用四分之一的输出空间。当您考虑到这些相互作用的约束的绝对规模时,是否存在同时满足总体要求的解决方案?

当然,为了显示(确切地说?)一个确实,这里是哈希结果值,按顺序列出,用于翻转0x7521d9318fbdf523 一位的输入(一个在时间),从 MSB(位置 63)向下到 LSB(0)。

3 2 0 3 3 3 3 3 3 0 0 0 3 0 3 3 0 3 3 3 0 0 3 3 3 0 0 3 3 0 3 3  // continued…
0 0 3 0 0 3 0 3 0 0 0 3 0 3 3 3 0 3 0 3 3 3 3 3 3 0 0 0 3 0 0 3  // notice: no '1' values

如您所见,没有 1 值,这意味着源中的每一位“原样”都必须对结果产生影响 (或者,如果您愿意,0x7521d9318fbdf523 中每一位的 de facto 状态对于防止整个整体结果为“not-@”是必要的 987654356@")。因为无论您对 64 位输入进行什么单位更改,2 位结果值都将不再是 1

请记住,上面显示的“缺失值”表是从仅对一个随机选择的示例值0x7521d9318fbdf523 的分析中得出的; 所有其他可能的输入值都有自己的类似表,每一个都奇怪地缺少其所有者的实际结果值,但在其集合成员中却以某种方式保持全局一致。此属性本质上对应于在(固有有损)位宽缩减任务期间最大程度地保留可用熵。

所以我们看到2⁶⁴ 中的每一个可能的源值都独立地对恰好 64 个其他源值施加了排除一个可能的结果值的约束。与我的直觉相反的是,这 64 个成员的集合中有数万亿个,每个成员也属于 63 个 other,看似无关的位旋转集合。然而不知何故,尽管存在这个最令人困惑的交织约束之谜,但利用一个(我猜想)同时完全满足它们的解决方案仍然是微不足道的。

所有这些似乎都与您可能在上表中注意到的一些事情有关:也就是说,我没有看到任何明显的方法可以将该技术扩展到压缩到 1 位的情况结果。在这种情况下,只有两个可能的结果值{ 0, 1 },因此,如果任何/每个给定(例如)64 位输入值仍然将其自己的结果排除在其所有 64 个单位翻转邻居的结果之外,那么现在基本上 强加 other,只剩下那些 64 的价值。我们在表格中看到的数学分解似乎表明在这种情况下同时出现的结果是一座桥太远了。

换句话说,XOR 的特殊'information-preserving' characteristic(也就是说,它非常可靠地保证,与 AND 不同,OR 等等,它 c̲a̲n̲w̲i̲l̲l̲ 总是会发生一点变化)毫不奇怪会产生一定的成本,即对一定数量的肘部空间的强烈不可协商的需求— 至少 2 位 — 可以使用。

【讨论】:

    【解决方案2】:

    我认为这是你能得到的最好的。您可以将代码压缩为一行,但 var 现在作为文档存在:

    function hash_32b_to_16b(val32b) {
        var rightBits = val32b & 0xffff; // Left-most 16 bits
        var leftBits = val32b & 0xffff0000; // Right-most 16 bits
    
        leftBits = leftBits >>> 16; // Shift the left-most 16 bits to a 16-bit value
    
        return rightBits ^ leftBits; // XOR the left-most and right-most bits
    }
    

    给定问题的参数,最佳解决方案将使每个 16 位散列正好对应 2^16 个 32 位数字。它还将 IMO 以不同的方式散列顺序 32 位数字。除非我遗漏了什么,否则我相信这个解决方案可以做到这两件事。

    我认为在这个问题中不能考虑安全性,因为散列值太少了。我相信我给出的解决方案可以将 32 位数字均匀分布到 16 位散列

    【讨论】:

    • 为什么你认为这是最好的?我认为对于有用且频繁的数字,它可能会发生大量冲突。
    • 这不是最好的主意。原因是 IP 地址通常被分配为连续的子网。这意味着如果 IP 地址 A.B.C.D 存在于网络中,则 A.(B^1).C.D 和 A.B.C.(D^1) 也存在的可能性稍大一些,并且会获得相同的哈希值。显然,任何哈希都会有很多冲突。但是您的方案将比您对统一挑选的 32 位整数进行散列所期望的冲突更多。多加一点,你会得到更好的结果。
    • 你用来评估散列函数质量的标准,即使是更简单的标准:hash = val&0xffff。但是,这些函数在现实生活中的数据上具有不同的冲突概率。
    • @Rostor Ha,您说得对,先生。所有这一切中涉及数百万美元的问题是可见的数据分布。
    【解决方案3】:

    这取决于整数的性质。 如果它们可以包含一些位掩码,或者可以相差 2 的幂,那么简单的 XOR 将具有很高的冲突概率。 你可以试试(i>>16) ^ ((i&0xffff) * p) 之类的东西,其中 p 是一个素数。

    像 MD5 这样的安全哈希都很好,但在这里它们显然是矫枉过正的。任何比 CRC16 更复杂的东西都是多余的。

    【讨论】:

    • 这是一个有趣的观点,显然与散列 IP 地址有关,是吗?
    • 是的。对于时间值 i&0xffff 通常应该足够了。 (希望没有睡眠(65536);任何地方:))
    • 除非您确切知道您将拥有哪些输入数据,否则无法判断什么是“足够的”。最坏情况下的碰撞次数仍然相同。与素数相乘只会使找到会系统地产生碰撞的现实情况变得更加困难。 (你的 delta-time 多久是 1009 的倍数?)为什么素数在这方面更好is a long discussion
    • 请记住,返回的数字可以超过 16 位。你可以这样做((i>>16) ^ ((i&0xffff) * p) & 0xffff)(但我不是专家)
    【解决方案4】:

    我会说只是应用一个标准哈希,如 sha1 或 md5,然后获取它的最后 16 位。

    【讨论】:

    • sha1 或 md5 的短输入流(如 4 个字节)是否存在问题?
    • sh1 和 md5 通常在 JavaScript 环境中不可用。是否有一些安全性稍差但大大简化的版本可以用几行 JS 来表达?
    【解决方案5】:

    假设您期望最低有效位“变化”最大,我认为您可能会通过仅使用值的低 16 位作为哈希来获得足够好的分布。

    如果您要散列的数字不具有这种分布,那么在高 16 位中进行异或的附加步骤可能会有所帮助。

    当然,如果您打算将哈希仅用于某种​​查找/存储方案,而不是寻找与加密相关的不可猜测性和不可逆转性(xor- ing 的建议也不会真正让你买账)。

    【讨论】:

      【解决方案6】:

      像这样简单的事情......

      function hash_32b_to_16b(val32b) {    
          var h = hmac(secretKey, sha512);
          var v = val32b;
          for(var i = 0; i < 4096; ++i)
              v = h(v);
          return v % 0xffff;
      }
      

      【讨论】:

      • 放慢速度。这是哈希密码的常用技术,使创建彩虹表或暴力密码的难度提高了几个数量级。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-30
      • 2011-10-25
      • 2011-06-10
      • 2013-09-18
      • 2019-08-02
      相关资源
      最近更新 更多