如果操作数是2的幂,则可以在没有模数运算符或除法的情况下轻松获得数字的模数。在这种情况下,以下公式成立:x % y = (x & (y − 1))
。在许多架构中,这通常很有效。可以对mod 31
进行同样的操作吗?
int mod31(int a){ return a % 31; };
答案 0 :(得分:8)
以下是解决此问题的两种方法。第一个使用常见的bit-twiddling技术,如果仔细优化可以击败硬件划分。另一个用乘法替换除法,类似于gcc
执行的优化,并且是最快的。最重要的是,如果第二个参数是常量,试图避开%
运算符没有多大意义,因为gcc
已经覆盖了它。 (也可能是其他编译器。)
以下函数基于x
与x
的基数32位数之和相同(mod 31)的事实。这是正确的,因为32
是1 mod 31
,因此32
的任何力量都是1 mod 31
。因此,基数为32的数字中的每个“数字”位置将数字* 1贡献给mod 31总和。并且很容易得到base-32表示:我们一次只取五位。
(与此答案中的其他功能一样,它仅适用于非负x
)。
unsigned mod31(unsigned x) {
unsigned tmp;
for (tmp = 0; x; x >>= 5) {
tmp += x & 31;
}
// Here we assume that there are at most 160 bits in x
tmp = (tmp >> 5) + (tmp & 31);
return tmp >= 31 ? tmp - 31 : tmp;
}
对于特定的整数大小,您可以展开循环并且很可能击败分区。 (请参阅@chux's answer了解将循环转换为O(log bits)
次操作而不是O(bits)
的方法更难以击败gcc
,这可避免在被除数为已知常数时进行除法在编译时。
在使用无符号32位整数的非常快速的基准测试中,天真的展开循环耗时19秒,基于@ chux答案的版本仅用了13秒,但gcc的x%31
耗时9.7秒。强制gcc使用硬件除法(通过使除法非常数)花费23.4秒,并且如上所示的代码花费25.6秒。这些数字应该用几粒盐。时间用于使用i%31
在我的笔记本电脑上为i
的所有可能值计算-O3 -march=native
。
gcc
通过用基本上64位乘法乘以常数的倒数后跟右移来避免32位除以常数。 (实际的算法可以做更多的工作来避免溢出。)该过程在20多年前的gcc v2.6
中实现,描述该算法的论文可以在gmp site上找到。 (GMP也使用这个技巧。)
这是一个简化版本:假设我们想为某些无符号32位整数n // 31
计算n
(使用pythonic //
来表示截断的整数除法)。我们使用“神奇常数”m = 232 // 31
,即138547332
。现在很明显,任何n
:
m * n <= 232 * n/31 < m * n + n
⇒ m * n // 232 <= n//31 <= (m * n + n) // 232
(这里我们使用a < b
然后floor(a) <= floor(b)
的事实。)
此外,由于n < 232
,m * n // 232
和(m * n + n) // 232
是相同的整数或两个连续的整数。因此,这两者中的一个(或两个)是n//31
的实际值。
现在,我们真的想要计算n%31
。所以我们需要将(假定的)商乘以31,然后从n
中减去该商。如果我们使用两个可能的商中较小的一个,可能会发现计算的模数值太大,但它只能太大了31。
或者,把它放在代码中:
static unsigned long long magic = 138547332;
unsigned mod31g(unsigned x) {
unsigned q = (x * magic) >> 32;
// To multiply by 31, we multiply by 32 and subtract
unsigned mod = x - ((q << 5) - q);
return mod < 31 ? mod : mod - 31;
}
gcc使用的实际算法通过使用基于乘以237//31 + 1
的稍微更准确的计算来避免最后的测试。这总是产生正确的商,但代价是一些额外的移位并增加以避免整数溢出。事实证明,上面的版本稍微快一些 - 在与上面相同的基准测试中,只用了6.3秒。
其他基准功能,完整性:
天真的展开循环
unsigned mod31b(unsigned x) {
unsigned tmp = x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31;
tmp = (tmp >> 5) + (tmp & 31);
return tmp >= 31 ? tmp - 31 : tmp;
}
@ chux的改进,略微优化
static const unsigned mask1 = (31U << 0) | (31U << 10) | (31U << 20) | (31U << 30);
static const unsigned mask2 = (31U << 5) | (31U << 15) | (31U << 25);
unsigned mod31c(unsigned x) {
x = (x & mask1) + ((x & mask2) >> 5);
x += x >> 20;
x += x >> 10;
x = (x & 31) + ((x >> 5) & 31);
return x >= 31 ? x - 31: x;
}
答案 1 :(得分:5)
[Edit2]下面的表现说明
只有1 if
条件的尝试。
这种方法是O(log2(sizeof unsigned))。如果代码使用uint64_t
,运行时间将增加1组ands / shifting / add而不是两倍的循环方法。
unsigned mod31(uint32_t x) {
#define m31 (31lu)
#define m3131 ((m31 << 5) | m31)
#define m31313131 ((m3131 << 10) | m3131)
static const uint32_t mask1 = (m31 << 0) | (m31 << 10) | (m31 << 20) | (m31 << 30);
static const uint32_t mask2 = (m31 << 5) | (m31 << 15) | (m31 << 25);
uint32_t a = x & mask1;
uint32_t b = x & mask2;
x = a + (b >> 5);
// x = xx 0000x xxxxx 0000x xxxxx 0000x xxxxx
a = x & m31313131;
b = x & (m31313131 << 20);
x = a + (b >> 20);
// x = 00 00000 00000 000xx xxxxx 000xx xxxxx
a = x & m3131;
b = x & (m3131 << 10);
x = a + (b >> 10);
// x = 00 00000 00000 00000 00000 00xxx xxxxx
a = x & m31;
b = x & (m31 << 5);
x = a + (b >> 5);
// x = 00 00000 00000 00000 00000 0000x xxxxx
return x >= 31 ? x-31 : x;
}
[编辑]
第一个加法方法将7个并行的7个组合并。随后的加法使7组进入4,然后是2,然后是1.最后的7位和然后继续将其上半部分(2位)加到其下半部分(5位)。然后代码使用一个测试来执行最终的“mod”。
此方法可扩展unsigned
更宽,至少uint165_t
log2(31 + 1)*(31 + 2)。通过它,需要更多的代码。
请参阅@rici以获得一些好的优化。仍建议在uint32_t
之类的转变中使用unsigned
与31UL
和31U << 15
,因为unsigned 31U
可能只有16位长。 (2014年在嵌入式世界中流行的16位int
)。
[EDIT2]
除了让编译器使用其优化器之外,另外两种技术可以提高性能。这些是更小的客厅技巧,取得了适度的改善。请注意YMMV,这适用于32位unsigned
。
使用查找最后modulo
的表格提高了10-20%。使用unsigned t
表而不是unsigned char t
也有点帮助。事实证明,表长度,首先预计需要2 * 31,只需要31 + 5。
使用局部变量而不是总是调用函数参数令人惊讶地帮助了。可能是我的gcc编译器的弱点。
找到未显示的非分支解决方案,以替换x >= 31 ? x-31 : x
。但是他们的编码复杂性更高,性能也更慢。
总而言之,这是一项有趣的运动。
unsigned mod31quik(unsigned xx) {
#define mask (31u | (31u << 10) | (31u << 20) | (31u << 30))
unsigned x = (xx & mask) + ((xx >> 5) & mask);
x += x >> 20;
x += x >> 10;
x = (x & 31u) + ((x >> 5) & 31u);
static const unsigned char t[31 * 2 /* 36 */] = { 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
return t[x];
}
答案 2 :(得分:2)
如果你想得到除以分母d
的模数,使得d = (1 << e) - 1
e
是指数,你可以使用{{1}的二进制展开的事实是每个1/d
个数字设置位的重复分数。例如,e
,e = 5
和d = 31
。
与rici’s answer类似,此算法有效地计算1/d = 0.0000100001...
的基数 - (1 << e)
数字的总和:
a
你可以展开这个循环,因为分母和分子中的位数都是常数,但让编译器这样做可能更好。当然,您可以将uint16_t mod31(uint16_t a) {
uint16_t b;
for (b = a; a > 31; a = b)
for (b = 0; a != 0; a >>= 5)
b += a & 31;
return b == 31 ? 0 : b;
}
更改为输入参数,将5
更改为根据该值计算的变量。
答案 3 :(得分:1)
您可以使用连续的加法/减法。没有其他技巧,因为31是一个素数,可以看出数字N
的模数是什么模型31你必须除以并找到余数。
int mode(int number, int modulus) {
int result = number;
if (number >= 0) {
while(result > modulus) { result = result - modulus;}
} else {
while (result < 0) { result = result + modulus;)
}
}
答案 4 :(得分:1)
int mod31(int a){
while(a >= 31) {
a -= 31;
}
return a;
};
如果a > 0
有效,但我怀疑它会比%
运算符更快。