模数已经解释过了,不过,让我们重述一下。
要找到k
模的余数2^n-1
,请写
k = a + 2^n*b, 0 <= a < 2^n
然后
k = a + ((2^n-1) + 1) * b
= (a + b) + (2^n-1)*b
≡ (a + b) (mod 2^n-1)
如果,重复直到余数a + b >= 2^n
小于_ _移位加后的位可能是位)。因此,如果类型的宽度与 相比较大,则需要多次移位 - 考虑 128 位类型,对于较大的类型,您将需要超过 40 次移位。为了减少所需的班次次数,您可以利用以下事实:2^n
a + b = 2^n-1
n
n
n
n-1
k < 2^(2*n-1)
2^n
n
n = 3
k
2^(m*n) - 1 = (2^n - 1) * (2^((m-1)*n) + 2^((m-2)*n) + ... + 2^(2*n) + 2^n + 1),
其中我们将只对所有人使用该2^n - 1
除法。然后,您移动该值的倍数,大约是该值在该步骤中可以具有的最大位长度的一半。对于上面的128位类型和余数模 7(2^(m*n) - 1
m > 0
n
2^3 - 1
r_1 = (k & (2^63 - 1)) + (k >> 63) // r_1 < 2^63 + 2^(128-63) < 2^66
得到一个最多 66 位的数字,然后移位 66/2 = 33 位
r_2 = (r_1 & (2^33 - 1)) + (r_1 >> 33) // r_2 < 2^33 + 2^(66-33) = 2^34
最多达到 34 位。下一个移位 18 位,然后是 9、6、3
r_3 = (r_2 & (2^18 - 1)) + (r_2 >> 18) // r_3 < 2^18 + 2^(34-18) < 2^19
r_4 = (r_3 & (2^9 - 1)) + (r_3 >> 9) // r_4 < 2^9 + 2^(19-9) < 2^11
r_5 = (r_4 & (2^6 - 1)) + (r_4 >> 6) // r_5 < 2^6 + 2^(11-6) < 2^7
r_6 = (r_5 & (2^3 - 1)) + (r_5 >> 3) // r_6 < 2^3 + 2^(7-3) < 2^5
r_7 = (r_6 & (2^3 - 1)) + (r_6 >> 3) // r_7 < 2^3 + 2^(5-3) < 2^4
现在一个减法r_7 >= 2^3 - 1
就足够了。要k % (2^n -1)
以 b 位类型计算,需要 O(log 2 (b/n)) 次移位。
商同样得到,我们再次写
k = a + 2^n*b, 0 <= a < 2^n
= a + ((2^n-1) + 1)*b
= (2^n-1)*b + (a+b),
就这样k/(2^n-1) = b + (a+b)/(2^n-1)
,我们继续一会儿a+b > 2^n-1
。不幸的是,在这里我们不能通过移动和掩蔽大约一半的宽度来减少工作,因此该方法仅在n
不小于类型宽度多少时才有效。
n
用于不太小的快速情况的代码:
unsigned long long modulus_2n1(unsigned n, unsigned long long k) {
unsigned long long mask = (1ULL << n) - 1ULL;
while(k > mask) {
k = (k & mask) + (k >> n);
}
return k == mask ? 0 : k;
}
unsigned long long quotient_2n1(unsigned n, unsigned long long k) {
unsigned long long mask = (1ULL << n) - 1ULL, quotient = 0;
while(k > mask) {
quotient += k >> n;
k = (k & mask) + (k >> n);
}
return k == mask ? quotient + 1 : quotient;
}
对于类型宽度一半的特殊情况n
,循环最多运行两次,所以如果分支开销很大,最好展开循环并无条件地执行循环体两次。