这个问题完全取决于能否确定整数中最左边的 1 的位置。一种方法是“正确涂抹位”,然后计算 1:
向右涂抹:
int smearright(int x) {
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x;
}
简单,只有位运算符。然而,计算位数涉及某种加法:
int popcnt(int x) {
x = add(x & 0x55555555, (x >> 1) & 0x55555555);
x = add(x & 0x33333333, (x >> 2) & 0x33333333);
x = add(x & 0x0f0f0f0f, (x >> 4) & 0x0f0f0f0f);
x = add(x & 0x00ff00ff, (x >> 8) & 0x00ff00ff);
x = add(x & 0xffff, (x >> 16) & 0xffff);
return x;
}
不过没关系,add可以实现为
int add(int x, int y) {
int p = x ^ y;
int g = x & y;
g |= p & (g << 1);
p &= p << 1;
g |= p & (g << 2);
p &= p << 2;
g |= p & (g << 4);
p &= p << 4;
g |= p & (g << 8);
p &= p << 8;
g |= p & (g << 16);
return x ^ y ^ (g << 1);
}
把它放在一起:
join = (left << popcnt(smearright(right))) | right;
如果你有加法(没有add 函数)显然要容易得多,但也许令人惊讶的是,它甚至比乘法更简单:
join = (left * (smearright(right) + 1)) | right;
再也没有popcnt了!
用位运算符实现乘法不会有帮助,更糟糕的是,我不确定你甚至可以用列出的运算符来做到这一点(除非右移是算术移位,但它仍然是一件可怕的事情涉及 32 个加法,每个加法本身都是函数)。
此答案中没有“偷偷摸摸的技巧”,例如使用隐式测试与零相等的条件(if、?:、while 等中的“隐藏”!= 0),以及控制流实际上是完全线性的(函数调用只是为了防止重复代码,一切都可以内联)。
这里有一个替代方案。不要使用popcnt,而是做一个奇怪的变量移位:
int shift_by_mask(int x, int mask) {
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
mask >>= 1;
x <<= mask & 1;
return x;
}
好吧,这并不让我高兴,但这是你如何使用它:
join = shift_by_mask(left, smearright(right)) | right;