最大限度保留某个原始32位“信号”的熵的关键在于确保每个32个输入位都具有独立且相等的能力来改变16位输出字的值。由于OP请求的位大小恰好是原始位的一半,因此满足此标准的最简单方法是对上半部分和下半部分进行异或操作,如其他人所提到的那样。使用异或操作是最优的,因为根据异或操作的定义,独立翻转任何一个32位输入位都保证会改变16位输出的值,这是显而易见的。
当你需要将输入从32位减少到2位时,问题变得更加有趣。记住,目标是尽可能保留源中的熵,因此那些简单地使用(i & 3)掩码处理最低的两个位的解决方案通常是错误的方向;这样做保证了除未掩码位之外的任何位都不会影响结果,这通常意味着运行时信号的任意可能有价值的部分被无原则地丢弃。从前面的段落可以得出,你当然可以使用xor进行三次迭代以产生具有所需属性的2位输出,即每个/任何输入位均受到同等影响。当然,这种解决方案仍然是最优的正确方案,但涉及循环或多个展开操作,而这些并不是必要的!
幸运的是,有一种只需要两个操作的好技巧,可以在这种情况下得到相同的最优结果。与xor
一样,它不仅确保对于任何给定的32位值,扭曲任何输入位都将导致2位输出的更改,而且还确保在给定输入值的均匀分布的情况下,2位输出值的分布也将完全均匀。在当前示例中,该方法将4,294,967,296
个可能的输入值分成恰好1,073,741,824
个四个可能的2位哈希结果{ 0, 1, 2, 3 }
。
我在这里提到的方法使用了我通过详尽搜索发现的特定魔法值,这些值似乎在互联网上没有被讨论得很多,至少对于此处讨论的特定用途(即确保最大熵保持均匀哈希分布)。奇怪的是,根据同样详尽的搜索,这些魔法值实际上是唯一的,这意味着对于每个目标位宽
{16、8、4、2}
,我下面展示的魔法值是唯一的值,当按照我在这里展示的方式使用时,满足上述完美哈希条件。
不再拖延,将32位哈希为n = {16、8、4、2}
的唯一且数学上最优过程是乘以与n
相对应的魔法值(无符号,舍弃溢出),然后取结果的n
高位。要将这些结果位隔离为[0 ... (2ⁿ - 1)]
范围内的哈希值,只需将乘法结果向右移动32 - n
位(无符号!)。
“神奇”的值和类C表达式语法如下:
方法
将32位减少到最大熵保留哈希. . .
目标位数 乘数 右移位数 表达式 [1, 2]
----------- ------------ ----------- -----------------------
16 0x80008001 16 (i * 0x80008001) >> 16
8 0x80808081 24 (i * 0x80808081) >> 24
4 0x88888889 28 (i * 0x88888889) >> 28
2 0xAAAAAAAB 30 (i * 0xAAAAAAAB) >> 30
将64位减少到最大熵保留哈希. . .
目标位数 乘数 右移位数 表达式 [1, 2]
----------- ------------------ ----------- -------------------------------
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 0xAAAAAAAAAAAAAAAB 62 (i * 0xAAAAAAAAAAAAAAAB) >> 62
注释:
- 使用无符号乘法,并丢弃任何溢出(不需要64位乘法)。
- 如果使用右移位来隔离结果(如图所示),请务必使用无符号移位操作。
进一步讨论
我觉得这很酷。在实际应用中,关键的信息理论要求是保证对于任何m位
输入值及其对应的n位
哈希值结果,翻转任何一个m
源位总是会导致n位
结果值发生变化。尽管总共有2ⁿ
种可能的结果值,但其中一种已经“被使用”(由结果本身),因为从任何其他结果“切换”到该结果将不会有任何变化。这留下了2ⁿ - 1
个结果值,可供整个由单个位翻转的m
输入值组成的集合使用。
让我们来考虑一个例子;事实上,为了展示这种技术可能看起来有点神秘或者非常神奇,我们将考虑更极端的情况,其中m = 64
和n = 2
。使用2个输出比特位,有四种可能的结果值,分别是{0, 1, 2, 3}
。假设一个任意的64位输入值0x7521d9318fbdf523
,我们会得到它的2位哈希值1
:
(0x7521d9318fbdf523 * 0xAAAAAAAAAAAAAAAB) >> 62 // result --> '1'
因此,结果为
1
,而声明是在单个位
0x7521d9318fbdf523
被切换的
64个值集合中,
没有任何一个值可能具有相同的结果值。也就是说,这64个
其他结果中没有一个可以使用值
1
,而必须使用
0
、
2
或
3
。因此,在这个例子中,似乎每一个2⁶⁴个输入值——除了另外64个输入值——都会自私地独占输出空间的
四分之一。考虑到这些交互约束的巨大数量,是否存在一个同时满足的解决方案?
嗯,当然,为了证明确实存在(精确地说),以下是哈希结果值的列表,按顺序列出了从最高位(位置63)向最低位(0)逐个翻转
0x7521d9318fbdf523
的单个位的输入。
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
中每一位的实际状态对于防止整个结果变为“非
1
”是至关重要的)。因为无论您对 64 位输入进行何种单比特更改,2 比特结果值都将不再是
1
。
请记住,上面显示的“缺失值”表格仅从分析一个随机选择的示例值
0x7521d9318fbdf523
中转储;每个其他可能的输入值都有自己的类似表格,每个表格都神秘地缺少其所有者的实际结果值,但在其集合成员中却以某种方式保持全局一致。这种属性基本上对应于在(固有的有损)位宽缩减任务期间最大程度地保留可用熵。
因此,我们可以看到每个可能的源值中的每一个独立地强加了在恰好 64 个其他源值上排除可能结果值的约束。使我感到困惑的是,有无数个这些 64 成员集合,每个成员也属于其他 63 个看似不相关的位操作集合。然而,尽管存在这种最令人费解的交织约束难题,却很容易利用其中一种(我猜测)解决方案,同时完全满足它们所有。
所有这些似乎都与您在上面的表格中注意到的某些内容有关:即,我没有看到任何明显的方法将技术扩展到压缩到 1 位结果的情况。在这种情况下,只有两个可能的结果值 {0,1},因此,如果任何/每个给定的(例如)64 位输入值仍然概括地排除其自身的结果成为其 64 个单位翻转邻居的所有结果之一,则这现在基本上会将另一个剩余值强加于这 64 个值上。我们在表格中看到的数学分解似乎在暗示这样的条件下的同时结果是一个太过遥远的目标。
换句话说,xor
的特殊'信息保留'特性(也就是它的高度可靠的保证,与and
、or
等不同,它总是可以并且将会改变一位)自然会有一定的代价,即强烈的非协商要求一定的操作空间——至少2个比特。