我正在尝试生成非顺序的人类可读订单代码,这些订单代码派生自(假设)一个从1开始的无符号32位内部ID,并为每个新订单自动递增。
在下面的示例代码中,每个$hash
都是唯一的吗? (我计划对$hash
进行base34编码,使其具有人类可读性。)
<?php
function int_hash($key) {
$key = ($key^0x47cb8a8c) ^ ($key<<12);
$key = ($key^0x61a988bc) ^ ($key>>19);
$key = ($key^0x78d2a3c8) ^ ($key<<5);
$key = ($key^0x5972b1be) ^ ($key<<9);
$key = ($key^0x2ea72dfe) ^ ($key<<3);
$key = ($key^0x5ff1057d) ^ ($key>>16);
return $key;
}
for($order_id = 1; $order_id <= PHP_INT_MAX; ++$order_id) {
$hash = int_hash($order_id);
}
?>
如果没有,是否有关于如何替换int_hash
的建议?
结果是,base34编码md5($order_id)
对我来说太长了。
答案 0 :(得分:17)
在下面的示例代码中,每个
$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
。
现在,我没有写出严格的证据,所以我只是给出一个单推理的例子,说明如何解决这个问题。假设$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)
不是。 (上面,当我写到两种类型的步骤“几乎等价”时,我真的意味着“几乎”。它有所不同!)你看,而<<
就像任何其他位一样处理符号位,并将其移出存在(在右侧引入一个零位),>>
特别处理符号位,并“扩展”它:它在左边引入的位等于符号位。 (N.B.你的问题提到“无符号32位”值,但PHP实际上并不支持它;它的按位操作总是在带符号的整数上。)
由于此符号扩展,如果$key
以0
开头,则$key >> NUM_BITS
以0
开头,如果$key
以{开头{ {1}},然后1
也以$key >> NUM_BITS
开头。在任何一种情况下,1
都会以$key ^ ($key >> NUM_BITS)
开头。你已经失去了一点熵。如果你给我0
,并且不告诉我$key ^ ($key >> 9)
是否为负数,那么我能做的最好就是为$key
计算两个可能的值:一个是负数,一个是正数 - 或者-Zero。
您执行两个使用右移而不是左移的步骤,因此您丢失了两位熵。 (我正在轻微地挥手 - 所有我实际证明的是你至少至少一位且最多两位 - 但我有信心,由于这些右移步骤之间的步骤的性质,你实际上会丢失两个完整位。)对于任何给定的输出值,有四个不同的输入值可以产生它。所以它不是唯一的,但它几乎独特;它很容易通过以下方式修复: