在研读Redis代码的时候偶然发现了这个网页:http://graphics.stanford.edu/~seander/bithacks.html,觉得很有趣于是就着位算法这个主题以Bit Twiddling Hacks的内容为主进行了一番梳理。后面会陆续补充更多的算法和应用场景与案例。
日期 | 内容 |
2020-02-23 | 版本v1.0,基础内容整理,少量实验测试和证明 |
运算符 |
特性 |
与 &,或 | |
相同运算符满足交换律、结合律,不同运算符满足分配律 |
非 ~,负 - |
~即按位取反; 按位取反再加1即得负数(补码表示),~v + 1 = -v; |
异或 ^ |
相同bit得0,不同bit得1; 任意数和0异或不变,任意数和1异或则取反,任意数和自己异或得0; 异或可展开:a^b = (~a & b) | (a & ~b); |
正整数返回+1,零返回0,负整数返回-1
四种算法,v=1234,循环一亿次记下总时间,单位毫秒:
算法 |
第一次 |
第二次 |
第三次 |
平均 |
|
1 |
v > 0 ? 1 : (v < 0 ? -1 : 0) |
133 |
134 |
133 |
133.3 |
2 |
(v != 0) | (v >> (sizeof(int) * CHAR_BIT - 1)) |
101 |
102 |
102 |
101.7 |
3 |
(v > 0) - (v < 0) |
101 |
100 |
100 |
100.3 |
4 |
(v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)) |
99 |
100 |
99 |
99.3 |
观察发现v取负数或0的时候,算法1的执行时间和算法2、3、4差不多。
测试环境是Intel i9-9900K,Windows10 Pro版,MinGW-W64,gcc 8.1.0-posix-sjlj-rt_v6-rev0,可见其分支指令的性能还是不错的。
int x, y; // 输入值
bool f = ((x ^ y) < 0); // 当且仅当x和y的符号不同的时候,返回true,相比用乘法判断,对于较大的x和y不会溢出
同样是一亿次计算统计总时间:
算法 |
第一次 |
第二次 |
第三次 |
平均 |
|
1 |
x < y ? x : y // min(x, y) x > y ? x : y // max(x, y) |
148 |
148 |
148 |
148 |
2 |
y ^ ((x ^ y) & -(x < y)) // min(x, y) x ^ ((x ^ y) & -(x < y)) // max(x, y) |
134 |
135 |
136 |
135 |
测试环境是Intel i9-9900K,Windows10 Pro版,MinGW-W64,gcc 8.1.0-posix-sjlj-rt_v6-rev0
unsigned int v; // 待判断的值
bool f = (v & (v - 1)) == 0; // 这里不包括0
bool f = v && !(v & (v - 1)); // 这里包括0
bool f; // 条件标记
unsigned int m; // bit掩码
unsigned int w; // 被修改的word,逻辑: if (f) w |= m; else w &= ~m;
w ^= (-f ^ w) & m;
// 或者对于超标量CPU:
w = (w & ~m) | (-f & m);
1. w ^= (-f ^ w) & m;
当f=1(true)时,表达式可写为w ^ ((-1 ^ w) & m),因为-1为全1,w无符号,所以-1 ^ w = ~w,表达式化简为w ^ (~w & m),根据异或的性质a ^ b = (~a & b) | (a & ~b),展开表达式为(~w & ~w & m) | (w & (~(~w & m))) = (~w & m) | (w & (w | ~m)) = (~w & m) | (w | (w & ~m)) = (~w & m) | w = (~w | w) & (m | w) = m | w
当f=0(false)时,表达式可写成w ^ ((0 ^ w) & m) = w ^ (w & m) = (~w & w & m) | (w & ~(w & m)) = 0 | (w & (~w | ~m)) = (w & ~w) | (w & ~m) = w & ~m
2. w = (w & ~m) | (-f & m);
当f=1(true)时,表达式可写为(w & ~m) | (-1 & m) = (w & ~m) | m = (m | w) & (m | ~m) = m | w
当f=0(false)时,表达式可写成(w & ~m) | (0 & m) = w & ~m
证毕
int v; // 目标整数
bool fNegate; // 是否需要对v符号取反的标志变量
int r = (v ^ -fNegate) + fNegate; // 等同于r = fNegate ? -v : v;
如果fNegative=1(true),那么r = (v ^ -1) +1 = ((~v & -1) | (v & ~-1)) + 1 = (~v | 0) + 1 = ~v + 1 = -v
如果fNegative=0(false),那么r = (v ^ 0) +0 = v
证毕
unsigned int a; // 非mask的整数
unsigned int b; // mask的整数
unsigned int mask; // 来自b的1和来自a的0
unsigned int r = a ^ ((a ^ b) & mask); // 等同于(a & ~mask) | (b & mask),但少一次操作,如果mask是constant,则没有什么优势了
unsigned int v; // 目标值
unsigned int c; // 计数结果
for (c = 0; v; v >>= 1) { // 最朴素的算法
c += v & 1;
}
复杂度:2*bitwise(v)
unsigned int v; // 目标值
unsigned int c; // 计数结果
for (c = 0; v; c++) {
v &= v - 1; // 清除低位的0
}
复杂度:2*c
static const unsigned char BitsSetTable256[256] = {
# define B2(n) n, n+1, n+1, n+2
# define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2)
# define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2)
B6(0), B6(1), B6(1), B6(2)
};
// 另一种程序化的方法生成表的方法:
BitsSetTable256[0] = 0;
for (int i = 0; i < 256; i++) {
BitsSetTable256[i] = (i & 1) + BitsSetTable256[i / 2];
}
unsigned int v; // 目标值
unsigned int c; // 计数结果
// 方法1:
c = BitsSetTable256[v & 0xff] +
BitsSetTable256[(v >> 8) & 0xff] +
BitsSetTable256[(v >> 16) & 0xff] +
BitsSetTable256[v >> 24];
// 方法2:
unsigned char * p = (unsigned char *) &v;
c = BitsSetTable256[p[0]] +
BitsSetTable256[p[1]] +
BitsSetTable256[p[2]] +
BitsSetTable256[p[3]];
复杂度:常量,32位整数12-13次操作
unsigned int v; // 目标值
unsigned int c; // 计数结果
v = v - ((v >> 1) & 0x55555555); // 每2位计数
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // 每4位计数
/* 与0x1010101做乘法可使最高字节为四个字节的和,所以最后结果就是右移24位取最高字节 */
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // 每8位计数
复杂度:常量,32位整数12次操作
原理说明参考:https://bits.stephan-brumme.com/countBits.html
unsigned int v; // 目标值
bool parity = false; // 奇偶校验结果,奇数个为true,偶数个为false
while (v) {
parity = !parity;
v = v & (v - 1);
}
复杂度:3*bitcount(v)
static const bool ParityTable256[256] = {
# define P2(n) n, n^1, n^1, n
# define P4(n) P2(n), P2(n^1), P2(n^1), P2(n)
# define P6(n) P4(n), P4(n^1), P4(n^1), P4(n)
P6(0), P6(1), P6(1), P6(0)
};
// 单字节数据的奇偶校验:
unsigned char b;
bool parity = ParityTable256[b];
// 32个bit位的奇偶校验,方法1:
unsigned int v; v ^= v >> 16; // 低16位和高16位异或叠加,利用异或特性,偶数次出现的1可以两两抵消不计
v ^= v >> 8; // 进一步按字节进行异或叠加
bool parity = ParityTable256[v & 0xff];
// 32个bit位的奇偶校验,方法2:
unsigned char * p = (unsigned char *) &v;
parity = ParityTable256[p[0] ^ p[1] ^ p[2] ^ p[3]]; // 按字节进行异或叠加
复杂度:常数,分别是1次、5次和4次操作
// 32位字长的算法:
unsigned int v; // 目标值
v ^= v >> 1;
v ^= v >> 2;
v = (v & 0x11111111U) * 0x11111111U;
bool parity = (v >> 28) & 1;
// 64位字长的算法:
unsigned long long v; // 目标值
v ^= v >> 1;
v ^= v >> 2;
v = (v & 0x1111111111111111UL) * 0x1111111111111111UL;
bool parity = (v >> 60) & 1;
复杂度:常数,8次操作(含1次乘法)
unsigned int v; // 目标值32位
v ^= v >> 16;
v ^= v >> 8;
v ^= v >> 4;
v &= 0xf;
bool parity = (0x6996 >> v) & 1;
复杂度:常数,9次操作
#define SWAP(a, b) ((&(a) == &(b)) || (((a) -= (b)), ((b) += (a)), ((a) = (b) - (a))))
如果确保a、b不是同一个地址,那么对a、b地址相同性的检测可以去掉;不要传入浮点数。
#define SWAP(a, b) (((a) == (b)) || (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))))
#define SWAP(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b))) // 可能会更快一点,因为a ^ b被复用
如果确保a、b不是同一个地址,那么对a、b地址相同性的检测可以去掉。
unsigned int i, j; // 待交换的序列的位置,从右算起
unsigned int n; // 每个序列连续的n位,从右向左
unsigned int b; // 原数据
unsigned int r; // 最终结果
unsigned int x = ((b >> i) ^ (b >> j)) & ((1U << n) - 1);
r = b ^ ((x << i) | (x << j));
基本原理同上面异或法交换两个值
unsigned int v; // 需要颠倒顺序的原数据
unsigned int r = v; // 最终结果
int s = sizeof(v) * CHAR_BIT - 1; // 在最后需要增加的移位
for (v >>= 1; v; v >>= 1) {
r <<= 1;
r |= v & 1;
s--;
}
r <<= s; // v的最高bit位是0地时候进行移位
复杂度:4*bitwise(v)
static const unsigned char BitReverseTable256[256] = {
# define R2(n) n, n + 2*64, n + 1*64, n + 3*64
# define R4(n) R2(n), R2(n + 2*16), R2(n + 1*16), R2(n + 3*16)
# define R6(n) R4(n), R4(n + 2*4 ), R4(n + 1*4 ), R4(n + 3*4 )
R6(0), R6(2), R6(1), R6(3)
};
unsigned int v; // 需要颠倒顺序的原数据,32位, 每次8比特
unsigned int c; // 最终结果
// 方法1:
c = (BitReverseTable256[v & 0xff] << 24) |
(BitReverseTable256[(v >> 8) & 0xff] << 16) |
(BitReverseTable256[(v >> 16) & 0xff] << 8) |
(BitReverseTable256[(v >> 24) & 0xff]);
// 方法2:
unsigned char * p = (unsigned char *) &v;
unsigned char * q = (unsigned char *) &c;
q[3] = BitReverseTable256[p[0]];
q[2] = BitReverseTable256[p[1]];
q[1] = BitReverseTable256[p[2]];
q[0] = BitReverseTable256[p[3]];
复杂度:常数,方法1是17次,方法2是12次
unsigned int v; // 需要颠倒顺序的原数据,32位
v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); // 交换奇数位和偶数位bit
v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); // 交换连续2个bit对
v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); // 交换连续4个bit对
v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); // 交换字节
v = (v >> 16) | (v << 16); // 交换双字节
以上是对32位数的处理,如果是更大的位数,就需要增加更多的交换计算。或者使用如下算法:
unsigned int s = sizeof(v) * CHAR_BIT; // bit size; must be power of 2
unsigned int mask = ~0;
while ((s >>= 1) > 0) {
mask ^= (mask << s);
v = ((v >> s) & mask) | ((v << s) & ~mask);
}
Redis 5.0.7的代码dict.c里面就使用了这个算法,在dictScan()函数中得到了应用。其中实现了一个逆向游标reversed cursor,是从高位bit进行递增,即先对游标的所有bit颠倒顺序,然后加1,然后再颠倒一下顺序。应用这个策略的原因是在两次游标迭代的之间,哈希表的大小(其实就是总容量)会发生改变,且无需重置游标。
const unsigned int n; // 分子
const unsigned int s;
const unsigned int d = 1U << s; // 所以d可能是: 1, 2, 4, 8, 16, 32, ...
unsigned int m = n & (d - 1); // n % d
unsigned int n; // 分子
const unsigned int s; // s > 0
const unsigned int d = (1 << s) - 1; // 所以d可能是:1, 3, 7, 15, 31, ...
unsigned int m; // n % d
for (m = n; n > d; n = m) {
for (m = 0; n; n >>= s) {
m += n & d;
}
}
m = m == d ? 0 : m;
unsigned int v; // 输入
int c; // 输出,比如v的二进制是1101000,那么c就是3
if (v) { // v不是0
v = (v ^ (v - 1)) >> 1; // 将拖尾的0全置为1,其余位置位0
for (c = 0; v; c++) {
v >>= 1;
}
}
else { // 全是0
c = CHAR_BIT * sizeof(v);
}
复杂度:O(N)
unsigned int v; // 目标值,32位
unsigned int c = 32; // 计数结果
v &= -signed(v);
if (v) c--;
if (v & 0x0000FFFF) c -= 16;
if (v & 0x00FF00FF) c -= 8;
if (v & 0x0F0F0F0F) c -= 4;
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;
复杂度:3 * lg(N) + 4
unsigned int v; // 输入
int c; // 输出,比如v的二进制是1101000,那么c就是3
// 注意如果0 == v,那么c = 31
if (v & 0x1) {
c = 0;
}
else {
c = 1;
if ((v & 0xffff) == 0) {
v >>= 16;
c += 16;
}
if ((v & 0xff) == 0) {
v >>= 8;
c += 8;
}
if ((v & 0xf) == 0) {
v >>= 4;
c += 4;
}
if ((v & 0x3) == 0) {
v >>= 2;
c += 2;
}
c -= v & 0x1;
}
复杂度:比上面的算法快大约33%
unsigned int v; // 输入
int r; // 结果
float f = (float)(v & -v); // v & ~v可以得到最低位的1和所有拖尾的0
r = (*(uint32_t *)&f >> 23) - 0x7f; // IEEE单精度浮点数的指数(exponent)部分被下移,并减去固定偏差(bias)127
unsigned int v; // 输入
int r; // 结果
static const int Mod37BitPosition[] = {
32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17, 0, 25, 22, 31, 15,
29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18
};
r = Mod37BitPosition[(-v & v) % 37]; // v & ~v可以得到最低位的1和所有拖尾的0
unsigned int v; // 输入
int r; // 结果
static const int MultiplyDeBruijnBitPosition[32] = {
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23,
21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];
进一步参考:http://supertech.csail.mit.edu/papers/debruijn.pdf
unsigned int const v; // 原数据
unsigned int r; // 结果 v=3 -> r=4; v=8 -> r=8
if (v > 1) {
float f = (float)v;
unsigned int const t = 1U << ((*(unsigned int *)&f >> 23) - 0x7f);
r = t << (t < v);
}
else {
r = 1;
}
unsigned int const v; // 原数据
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++; // 结果等同于1U << (lg(v - 1) + 1)
unsigned short x; // x的所有bit会被放置在偶数位
unsigned short y; // y的所有bit会被放置在奇数位
unsigned int z = 0; // 存储结果,即莫顿码Morton Number
for (int i = 0; i < sizeof(x) * CHAR_BIT; i++) { // unroll for more speed...
z |= (x & 1U << i) << i | (y & 1U << i) << (i + 1);
}
static const unsigned short MortonTable256[256] = {
0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, 0x0040, 0x0041, 0x0044,
0x0045, 0x0050, 0x0051, 0x0054, 0x0055, 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111,
0x0114, 0x0115, 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, 0x0400,
0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, 0x0440, 0x0441, 0x0444, 0x0445,
0x0450, 0x0451, 0x0454, 0x0455, 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514,
0x0515, 0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, 0x1000, 0x1001,
0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 0x1040, 0x1041, 0x1044, 0x1045, 0x1050,
0x1051, 0x1054, 0x1055, 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115,
0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404,
0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451,
0x1454, 0x1455, 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, 0x1540,
0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, 0x4000, 0x4001, 0x4004, 0x4005,
0x4010, 0x4011, 0x4014, 0x4015, 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054,
0x4055, 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, 0x4140, 0x4141,
0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 0x4404, 0x4405, 0x4410,
0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455,
0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544,
0x4545, 0x4550, 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011,
0x5014, 0x5015, 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, 0x5100,
0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 0x5140, 0x5141, 0x5144, 0x5145,
0x5150, 0x5151, 0x5154, 0x5155, 0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414,
0x5415, 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, 0x5500, 0x5501,
0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, 0x5545, 0x5550,
0x5551, 0x5554, 0x5555
};
unsigned short x; // x的所有bit会被放置在偶数位
unsigned short y; // y的所有bit会被放置在奇数位
unsigned int z = 0; // 存储结果,即莫顿码Morton Number
z = MortonTable256[y >> 8] << 17 |
MortonTable256[x >> 8] << 16 |
MortonTable256[y & 0xFF] << 1 |
MortonTable256[x & 0xFF];
unsigned short x; // x的所有bit会被放置在偶数位
unsigned short y; // y的所有bit会被放置在奇数位
unsigned int z = 0; // 存储结果,即莫顿码Morton Number
z = ((x * 0x0101010101010101ULL & 0x8040201008040201ULL) * 0x0102040810204081ULL >> 49) &
0x5555 |
((y * 0x0101010101010101ULL & 0x8040201008040201ULL) * 0x0102040810204081ULL >> 48) &
0xAAAA;
static const unsigned int B[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF };
static const unsigned int S[] = { 1, 2, 4, 8 };
unsigned short x; // x的所有bit会被放置在偶数位
unsigned short y; // y的所有bit会被放置在奇数位
unsigned int z = 0; // 存储结果,即莫顿码Morton Number,x和y都要小于65536
x = (x | (x << S[3])) & B[3];
x = (x | (x << S[2])) & B[2];
x = (x | (x << S[1])) & B[1];
x = (x | (x << S[0])) & B[0];
y = (y | (y << S[3])) & B[3];
y = (y | (y << S[2])) & B[2];
y = (y | (y << S[1])) & B[1];
y = (y | (y << S[0])) & B[0];
z = x | (y << 1);
unsigned int v; // 32位原数据,检测其是否有一个8bit字节是0
bool hasZeroByte = ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F);
v & 0x7F7F7F7F:将每一个字节的第一位置为0,其余低位保留;
(v & 0x7F7F7F7F) + 0x7F7F7F7F:如果字节最高位是1就意味着其余低位至少有一个1,如果字节最高位是0就意味着其余低位都是0;
((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v:字节最高位是1,当且仅当字节至少存在一个bit是1;
~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F):字节低位都是0,最高位如果是1就说明原字节没有1,如果是0就说明原字节至少有一个1(这里有点绕,因为取了反)。
复杂度:常数,5次操作,64位数据把0x7F7F7F7F换成0x7F7F7F7F7F7F7F7F即可,也是5次操作。
更加直观的方法:
bool hasNoZeroByte = ((v & 0xff) && (v & 0xff00) && (v & 0xff0000) && (v & 0xff000000))
// 或者:
unsigned char * p = (unsigned char *) &v;
bool hasNoZeroByte = *p && *(p + 1) && *(p + 2) && *(p + 3);
#define hasvalue(x,n) (haszero((x) ^ (~0UL/255 * (n)))) // 利用haszero和异或特性:和相同值异或得0
#define hasless(x,n) (((x) - ~0UL/255*(n)) & ~(x) & ~0UL/255*128)
#define countless(x,n) (((~0UL/255*(127+(n))-((x)&~0UL/255*127))&~(x)&~0UL/255*128)/128%255)
#define hasmore(x,n) (((x)+~0UL/255*(127-(n))|(x))&~0UL/255*128)
#define countmore(x,n) (((((x)&~0UL/255*127)+~0UL/255*(127-(n))|(x))&~0UL/255*128)/128%255)
#define likelyhasbetween(x,m,n) \ ((((x)-~0UL/255*(n))&~(x)&((x)&~0UL/255*127)+~0UL/255*(127-(m)))&~0UL/255*128)
#define hasbetween(x,m,n) \ ((~0UL/255*(127+(n))-((x)&~0UL/255*127)&~(x)&((x)&~0UL/255*127)+~0UL/255*(127-(m)))&~0UL/255*128)
#define countbetween(x,m,n) (hasbetween(x,m,n)/128%255)
unsigned int v; // 当前的bit排列
unsigned int w; // 下一个bit排列
unsigned int t = v | (v - 1); // 将低位的连续0都置为1
// Next set to 1 the most significant bit to change,
// set to 0 the least significant ones, and add the necessary 1 bits.
// __builtin_ctz是GNU C编译器用于x86 CPU的内置指令,用于计算拖尾的0的数量,可根据平台和编译器版本更换,或者使用前面提到的方法
w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));
另一种使用除法但无需计数拖尾0的数量的方法:
unsigned int t = (v | (v - 1)) + 1;
w = t | ((((t & -t) / (v & -v)) >> 1) - 1);