摘录自《算法新的-高效算法的奥秘(第二版)》
操作最右边的位元
将字组中值为 1 且最靠右的位元“关闭”,如果不存在值为 1 的位元,那结果就是 0(例如 01011110 => 01010000):
1 | x & (x一1) |
该操作可判断某个无符号整数是不是 2 的幂或 0:套用此公式后判断运行结果是否为 0 即可。
将字组中值为 0 且最靠右的位元“打开”,如果不存在值为 0 的位元,则结果的每一位都是 1(例如 10100111 => 10101111):
1 | x | (x+1) |
将字组尾部的 1 都变成 0,如果尾部没有 1,则x不变(例如 10100111 => 10100000):
1 | x & (x+1) |
套用此公式后判断结果是否为 0,即可确定某个无符号整数是不是 2^n^-1 或 0,也可以判断某个数的所有位元是否均为 1。
将字组尾部的 0 都变成 1,如果尾部没有 0,则 x 不变(例如10101000 => 10101111):
1 | x | (x-1) |
把 x 中最靠右且值为 0 的位元变为 1,并将其余位元置 0。如果x中没有值为 0 的位元,则结果为 0(例如 10100111 => 00001000):
1 | !x & (x+1) |
把 x 中最靠右且值为 1 的位元变成 0,并将其余位元置 1。如果 x 中没有值为 1 的位元,则运算结果中的每个位元均是 1(例如 10101000 => 11110111):
1 | !x | (x-1) |
下列 3 个公式都可以把字组尾部所有值为 0 的位元变成 1,并将其余位元置 0。如果 x 中没有值为 0 的位元,则结果是 0(例如 01011000 => 00000111):
1 | !x & (x-1) |
第 1 个公式可以发挥处理器的某些指令级并行能力。
下列公式可以把字组尾部所有值为 1 的位元都变成 0,并将其余位元设为 1。如果 x 中没有值为 1 的位元,则运算结果中的每个位元均是 1(例如 10100111 => 11111000):
1 | !x | (x+1) |
下列公式可以保留字组中最靠右且值为 1 的位元,并将其余位元置 0。若 x 不存在值为 1 的位元,则运算结果为 0(例如 01011000 => 00001000):
1 | x & (-x) |
下列公式可以将字组最靠右且值为 1 的位元,及其右方所有值为 0 的位元都变成 1,并将左方位元置 0。若 x 中没有值为 1 的位元,则运算结果的每一位都是 1,而当x尾部没有值为 0 的位元时,运算结果是 1(例如 01011000 => 00001111):
1 | x ^ (x-1) |
下列公式可以将字组最靠右且值为 0 的位元,及其右方所有值为 1 的位元都设为 1,并将左方位元置 0。若 x 中没有值为 0 的位元,则运算结果的每一位都是 1,而当 x 尾部没有值为 1 的位元时,运算结果是 1(例如 01010111 => 00001111):
1 | x ^ (x+1) |
下列两个公式都可以把字组右侧连续出现且值为 1 的位元置 0(例如 01011100 => 01000000):
1 | ( (x | (x-1)) + 1) & x |
如果 x 是非负数,而且套用上述公式运算的结果是 0,那就表明它可以写为 2^j^-2^k^ 的形式。其中 j ≥ k ≥ 0。
德摩根定律及推论
描述德摩根定律的两个逻辑恒等式可以理解为把取反符号(not sign)“分配”到每一个变量中。结合下列等式(头两个就是德摩根定律)与德摩根定律的思路,可以为所讲的公式及其他一些公式变形。
1 | !(x & y) = !x | !y |
上述公式可以这样用:
1 | !(x | -(x + 1)) = !x & !-(x + 1) = -!x & ((x + 1) - 1) = !x & x = 0 |
结合逻辑操作的加减运算
1 | -x = !x + 1 |
逻辑与算数表达式中的不等式
1 | (x ^ y) ≤ (x | y) |
绝对值函数
abs | nabs |
---|---|
(x ^ y) - y | y - (x ^ y) |
(x + y) ^ y | (y - x) ^ y |
x - (2x & y) | (2x & y) - x |
若 CPU 能快速算出一个数与 ±1 的乘积,那么可以考虑用这个式子求绝对值:
1 | ((x >> 30) | 1) * x |
两数平均值
下列公式算出两个无符号数的平均值 (x+y)/2
,而且还不会溢出:
1 | (x & y) + ((x ^ y) >> 1) |
若是无符号整数:
1 | (x | y) - ((x ^ y) >> 1) |
符号
!
优先级较高,+
-
优先级大于<<
>>
,再大于&
|
^
对于两个带符号的整数,有时可能要计算向 0 取整之后的平均值。可以先算出向下取整之后的平均值,然后再修正。修正它的办法是:如果 x+y 的算术值是负奇数,则将结果再加 1。然而当且仅当上面第一式运算结果为负时(其中的无符号移位变成带符号移位),x+y 才会是负数。
1 | t = (x & y) + ((x ^ y) >> 1) |
有些特例可以用更快的办法算出来。比如假设 x 和 y 都是带符号的非负数,那么只需要算出 (x+y)>>1 就能知道其平均值了。虽然两数之和可能会溢出,但是溢出的那一位仍然保留在存放加法结果的寄存器里,所以只需要执行一次无符号右移,就可以把这个溢出位挪到正确的地方,并且让空出来的那个符号位填上 0。
如果 x 和 y 都是无符号整数且 x≤y,或是 x、y 都是带符号整数且 x≤y(带符号数的比较),那么平均值就是 (y-x)>>1。这样求出来的平均值是向下取整过的,比如 -1 与 0 的平均值是 -1。
符号函数
1 | sign(x) = { |
大部分电脑只需要 4 个指令即可实现:
1 | (x >> 31) | (-x >> 31) |
假若CPU没有带符号右移指令,模拟出来算式:
1 | -(x >> 31) | (-x >> 31) |
如果加入比较谓词,只需要 3 个指令:
1 | (x > 0) - (x < 0) |
还有一个公式:(-x >> 31) - (x >> 31)
,仅 x = -2^31^ 的情况外,也能算出绝对值。
三值比较函数
1 | cmp(x, y) = { |
1 | (x > y) - (x < y) |
附录
位运算符说明
符号 | 说明 |
---|---|
& | 位与 |
| | 位或 |
^ 、⊕ | 异或 |
! | 取反 |
≡ | 按位等值,等同于 !(x ^ y) |