Lesson 3 : Bit Hacks
1. Binary Representaion
x + ~x = -1 // 现在所有位均为1,补码均为1时结果为-1
-x = ~x + 1 // 由上式可推出
2. Some Tricks
下面的代码可以通过c实现,别的语言可能无法实现。
2.1 No-Temp Swap
x = x ^ y;
y = x ^ y;
x = x ^ y;
这是因为(x ^ y) ^ y => x;
证明如下:
x = x ^ y, 这时x储存的值设定为了:x,y相同的数据的位置的值为0,不同数据的位置的值为1。
y = x ^ y, x, y相同数据的位置的值为0,与y异或,得到的值为x,y中相同的数据,x,y不同数据的位置的值为1,与y异或,得到的值与y中数据相反,即为x中的数据。现在,y值就变成了x,因为相同数据的位置的值不变,不同数据的值取反。
按照上面的推理可以推出x = x ^ y后x的值为之前y的值。
这种方式并不一定高效,因为这三个指令必须顺序执行。如果采用temp的方式,后面的两个指令可以并行执行。
2.2 No-Branch Minimum
r = y ^ ((x ^ y) & -(x < y))
证明如下:
情况1,x < y:则-(x < y) = -1,即所有位均为1,任何值&所有位均为1的数据还是其本身,于是 r = y ^ (x ^ y),根据2.1中的证明, y ^ (x ^ y) = x,于是,r = x。
情况2,x > y: 则-(x > y) = 0,即所有位均为0,任何值&所有位均为0的数据还是0,于是r = y ^ (0) = y。证毕。
优化分支的方式在大多数现在编译器上效果不好,这是因为编译器会对分支进行优化,比我们简单处理的优化结果要好
2.3 No-Branch Mod
Problem: z = x + y, 0 ≤ x < n, 0 ≤ y < n, r = z % n, 求r
//Normal
z = x + y;
if(z > r) r = z - n;
else r = z;
//equal to r = (z > n) ? z - n : z
//No-Branch
r = z -(n & (z ≥ n))
证明方法与2.2类似。
2.4 Least Significant
Problem: 找到x中最低位的1
r = x & (-x);
证明:因为-x = ~x + 1,假设x中第i位是最低位的1,所以比i位低的位均为0,那么取反之后比i位低的位均为1,第i位为0,比i位高的为相应位置x取反。加上1之后,比i位低的位均变成了0,而第i位为1,比i位高的仍为相应位置x取反。我们可以发现,比i高的位是x取反,i位及低于i位的与x相同,但第i位为1,低于i位的为0。所以,经过&操作后,r值第i位为1,其余位置均为0。
PS:可以通过lgr的方式求解i。
2.5 Population Count 1
Problem: 计算x中1的数量
for(r = 0; x != 0; ++r)
x &= x -1;
证明:我们看一下x &= x -1的值究竟是什么,我们假设第i位为最低位的1,那么在x中第i为1,比第i位低的为0,在x-1中,第i位为0,比第i位低的为1,比i高的位中数据相同。那么x &= x -1就等于x将最低位的1变成0的后值,即记录了一个1。
我们可以发现,上面的代码执行速度与x中1的数量正相关,下面给出与1的数量无关的算法。
//我们假设使用的机器为64位
M0 = ~(-1 << 32); //
M1 = M0 ^(M0 << 16);
M2 = M1 ^(M1 << 8);
M3 = M2 ^(M2 << 4);
M4 = M3 ^(M3 << 2);
M5 = M4 ^(M4 << 1);
x = ((x >> 1) & M5) + (x & M5);
x = ((x >> 2) & M4) + (x & M4);
x = ((x >> 4) + x) & M3;
x = ((x >> 8) + x) & M2;
x = ((x >> 16) + x) & M1;
x = ((x >> 32) + x) & M0;
第一次计算出的为每2位1的数量,存到x,
第二次计算出的为每4位1的数量,存到x,
第三次计算出的为每8位1的数量,存到x,
第四次计算出的为每16位1的数量,存到x,
第五次计算出的为每32位1的数量,存到x,
第六次计算出的为每64位1的数量,存到x。
2.6 Board Representation
Problem: 如何表示八皇后的状态,最节省空间的做法是什么
- n*n的矩阵(byte)
- n*n的矩阵(bit)
- n大小的数组(byte)
- 3 bitvectors of size n, 2n-1, and 2n-1(bit).
方式4是最为节省空间的做法,采用down数组存放列状态,left数组存放左斜状态,right数据存放右斜状态。(数组中的每个元素大小为bit)
方式3是最为简单的做法,数组的下标即为列,再存上对应的行数,即能够确定整个八皇后的状态。