这个计数魔法是如何工作的?

时间:2013-04-08 15:09:32

标签: bit-manipulation

在处理XKCD愚人节skein hash collision problem时,我遇到了strange, fast, multiplicative method计算单词中的设置位:

c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

为什么这样做/正在发生什么?我们可以推广这种方法(例如,为了解决问题中的128位值)吗?

另外,我不禁认为这与moving bits around using a clever magic number的问题有关。

1 个答案:

答案 0 :(得分:2)

实际上,这不计算32位字中的设置位,因为模运算符的性质输出必须小于0xf(a.k.a。15)。

首先,让我们特别注意模运算符。为什么15?为什么我们要掩盖每个nybble中最不重要的位?

请注意,对于某些16^k,每个最不重要的nybble位的值为k。请注意,16 mod 15为1,因此对于任何非负整数值16^k mod 15k为1。

这很方便,因为它意味着16^k1 + 16^k2 + ... + 16^kn = n mod 15

换句话说,模运算符有效地计算由于上述数学而设置的最低有效nybble位的数量 - 只要设置了nybbles中的其他位。 (他们只是妨碍了。)

但是,我们不想只计算nybbles中特殊格式的位。我们想要计算任意值中设置的位数。诀窍是通过移动位来将这些值位转换为特殊格式的nybbles。只要我们可以将一个值移动到一个nybble,nybbles的最终顺序并不重要。理论上,因为我们使用64位值来进行计数,所以我们可以将16位值中的每个位映射到它自己的nybble,总共得到4 * 16 = 64位,就在我们的64位容差范围内。但请注意,因为我们使用的是模15,所以15或16位设置的任何值都将分别显示为0或1。

现在让我们重新关注奇怪的常量:0x200040008001ULL

让我们注意哪些位被设置(其中位0是最低有效位):0,15,30和45.您可能已经注意到它们以15位间隔隔开。这很方便,因为对于小于2^15的值,此乘法只会在64位字中创建值的多个移位副本。但是当值变得等于或大于2^15时,副本开始叠加重叠,这对于特别计数位不再有用。但这没关系,因为通过模数运算,我们甚至无法可靠地计算多达15位的信息。 (但是,如果模运算的结果为0,我们知道所有位都没有设置,或者没有设置,再次假设我们只得到小于2 ^ 15的值。)

因此,我们在64位寄存器中移位了15位数字的副本。第二步是掩码仅提取每个nybble的最低有效位。因为每个nybble的最低有效位等于1 (mod 15),所以模运算符有效地计算了在nybbles中设置的最低有效位的数量。

剩下的唯一细节是确保我们的15位数字中的每个位都在一个最不重要的nybble位插槽中完全一次。

我们来看看:

The first bit set, 0, doesn't shift the value at all, giving our value bits 0 through 14.
This places value value bits 0, 4, 8, and 12 in a least significant nybble bit slot.

The second bit set, 15, gives our value bits 15 through 29.
This places our value bits 1, 5, 9, and 13 in bits 16, 20, 24, and 28.

The third bit set, 30, gives our value bits 30 through 44.
This places our value bits 2, 6, 10, and 14 in bits 32, 36, 40, and 44.

Finally, the forth bit set, 45, gives our value bits 45 through 59.
This places our value bits 3, 7, 11, and 15 in bits 48, 52, 56, and 60.

Bits accounted for:
0, 4, 8,  and 12
1, 5, 9,  and 13
2, 6, 10, and 14
3, 7, 11, and 15

很容易在视觉上验证这是否映射了16位。但是,请注意,掩码实际上是15 1,而不是16.因此位置在最后一个nybble中(从第60位开始,表示我们值的第15位,16位值的最高位)实际上被忽略了。

有了这个,总技术就完成了:

  1. 使用乘法将每个位映射到最不重要的nybble位。
  2. 使用遮罩仅选择所需的nybble位。
  3. 请注意,最不重要的nybble位相当于1 (mod 15)
  4. 因此,(mod 15)只会将这些位加在一起......最多可设置14位。