在下面的示例代码中,每个 $hash 是否都是唯一的?
几乎。(我猜,这意味着“不,但以一种很容易修复的方式”。)您的功能由一系列独立的步骤组成;当且仅当这些步骤中的每一个都是双射的(可逆的)时,整个函数才是双射的。 (你明白为什么吗?)
现在,每个步骤都有以下形式之一:
$key = ($key ^ CONSTANT) ^ ($key >> NUM_BITS);
$key = ($key ^ CONSTANT) ^ ($key << NUM_BITS);
NUM_BITS != 0.
我们实际上可以将它们视为单一形式的变体,将前者视为几乎相当于:
$key = invert_order_of_bits($key); # clearly bijective
$constant = invert_order_of_bits(CONSTANT);
$key = ($key ^ $constant) ^ ($key << NUM_BITS);
$key = invert_order_of_bits($key); # clearly bijective
所以我们只需要证明这一点:
$key = ($key ^ CONSTANT) ^ ($key << NUM_BITS);
是双射的。现在,XOR 是可交换的和关联的,所以上面等价于:
$key = $key ^ ($key << NUM_BITS);
$key = $key ^ CONSTANT;
和(x ^ y) ^ y == x ^ (y ^ y) == x ^ 0 == x,很明显,与一个常数值的异或是可逆的(通过与相同的值重新异或);所以我们要证明的是这是双射的:
$key = $key ^ ($key << NUM_BITS);
无论何时NUM_BITS != 0。
现在,我不是在写一个严格的证明,所以我只给出一个 single 合理的例子来说明如何扭转这一点。假设$key ^ ($key << 9)是
0010 1010 1101 1110 0010 0101 0000 1100
我们如何获得$key?好吧,我们知道$key << 9的最后九位都是零,所以我们知道$key ^ ($key << 9)的最后九位和$key的最后九位是一样的。所以$key 看起来像
bbbb bbbb bbbb bbbb bbbb bbb1 0000 1100
所以$key << 9 看起来像
bbbb bbbb bbbb bb10 0001 1000 0000 0000
所以$key 看起来像
bbbb bbbb bbbb bb00 0011 1101 0000 1100
(通过对$key ^ ($key << 9) 与$key << 9 进行异或运算),所以$key << 9 看起来像
bbbb b000 0111 1010 0001 1000 0000 0000
所以$key 看起来像
bbbb b010 1010 0100 0011 1101 0000 1100
所以$key << 9 看起来像
0101 1000 0111 1010 0001 1000 0000 0000
所以$key 看起来像
0111 0010 1010 0100 0011 1101 0000 1100
所以。 . .为什么我说“几乎”而不是“是”?为什么你的散列函数不是完美双射的?这是因为在 PHP 中,位移运算符>> 和<< 不是相当 对称的,而$key = $key ^ ($key << NUM_BITS) 是完全可逆的,$key = $key ^ ($key >> NUM_BITS) 不是。 (上面,当我写到这两种类型的步骤“几乎等效”时,我真的意思是“几乎”。这会有所不同!)你看,而<< 像对待任何其他位一样对待符号位,并将其移出(在右侧引入一个零位),>> 特别对待符号位,并“扩展”它:左边带入等于符号位。 (注意,您的问题提到了“无符号 32 位”值,但 PHP 实际上并不支持它;它的按位运算总是在 signed 整数上。)
由于这个符号扩展,如果$key以0开头,那么$key >> NUM_BITS以0开头,如果$key以1开头,那么$key >> NUM_BITS也以$key >> NUM_BITS开头1。无论哪种情况,$key ^ ($key >> NUM_BITS) 都将以0 开头。你已经失去了一点熵。如果你给我$key ^ ($key >> 9),但不告诉我$key 是否为负,那么我能做的最好的就是计算$key 的两个可能值:一个负,一个正或零。
您执行了两个使用右移而不是左移的步骤,因此您丢失了两位熵。 (我稍微挥手——我实际证明的只是你失去至少一位,至多两位——但我有信心,由于这些右移步骤之间的步骤的性质,您实际上会丢失两个完整的位。)对于任何给定的输出值,恰好有四个不同的输入值可以产生它。所以它不是唯一的,但它几乎是唯一的;它很容易通过以下方式修复:
- 将两个右移步骤改为使用左移;或
- 将两个右移步骤移到函数的开头,在任何左移步骤之前,并说输出对于 0 到 231-1 之间的输入是唯一的,而不是输入介于 0 和 232-1.