有用的位运算Bit Twiddling Hacks

在研读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


判断一个整数是否是2的幂

unsigned int v; // 待判断的值 
bool f = (v & (v - 1)) == 0; // 这里不包括0
bool f = v && !(v & (v - 1)); // 这里包括0

按条件设置或清除bit位(不带分支判断)

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

证毕


通过一个掩码合并来自两个整数的bit位

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,则没有什么优势了

计数bit集(多少个1):朴素算法

unsigned int v; // 目标值
unsigned int c; // 计数结果 
for (c = 0; v; v >>= 1) { // 最朴素的算法 
  c += v & 1; 
}

复杂度:2*bitwise(v)


计数bit集:Brian Kernighan方法

unsigned int v; // 目标值
unsigned int c; // 计数结果
for (c = 0; v; c++) {
  v &= v - 1; // 清除低位的0
}

复杂度:2*c


计数bit集:查表法

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次操作


计数bit集:并行

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


bit位的奇偶校验(1有奇数个还是偶数个):朴素算法

unsigned int v; // 目标值
bool parity = false; // 奇偶校验结果,奇数个为true,偶数个为false 
while (v) { 
  parity = !parity; 
  v = v & (v - 1);
}

复杂度:3*bitcount(v)


bit位的奇偶校验:查表法

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次操作


bit位的奇偶校验:使用乘法

// 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次乘法)


bit位的奇偶校验:并行

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地址相同性的检测可以去掉。


交换bit位:异或法

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));

基本原理同上面异或法交换两个值


将bit位头尾颠倒顺序:朴素算法

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)


将bit位头尾颠倒顺序:查表法

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次


将bit位头尾颠倒顺序:5*lg(N)算法

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,然后再颠倒一下顺序。应用这个策略的原因是在两次游标迭代的之间,哈希表的大小(其实就是总容量)会发生改变,且无需重置游标。


计算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

计算(1<
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;

计数右边的连续0比特:线性方法

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)


计数右边的连续0比特:并行方法

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


计数右边的连续0比特:二分法

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%


计数右边的连续0比特:转换成float

unsigned int v; // 输入 
int r; // 结果 
float f = (float)(v & -v); // v & ~v可以得到最低位的1和所有拖尾的0 
r = (*(uint32_t *)&f >> 23) - 0x7f; // IEEE单精度浮点数的指数(exponent)部分被下移,并减去固定偏差(bias)127

计数右边的连续0比特:取模和查表法

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

计数右边的连续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


向上取最近的2的幂:转换成float

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; 
}

向上取最近的2的幂

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)

交织两个数的bit位:朴素方法

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); 
}

交织两个数的bit位:查表法

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];

交织两个数的bit位:乘法

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;


交织两个数的bit位:魔法数

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);

判断一个字是否有0字节

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);

判断是否存在一个字节等于n

#define hasvalue(x,n) (haszero((x) ^ (~0UL/255 * (n)))) // 利用haszero和异或特性:和相同值异或得0

判断是否存在一个字节小于n

#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)

判断是否存在一个字节大于n

#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)

判断是否存在一个字节大于m小于n

#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)

计算字典序的下一个bit排列

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);

 

你可能感兴趣的:(算法,c语言)