神奇的位运算
Contents
In editing…
必备知识
首先要对原码、反码、补码有一定理解,推荐阅读此文:https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/computercode.html
摘要:按 1 byte 来算,1 的机器数为 0000 0001,原码、反码、补码均为 0000 0001. -1 的机器数为 1000 0001,原码为机器数、反码为 1111 1110,补码为 1111 1111. 总结来说:最高位用作符号位,0 表示正数,1 表示负数。正数的原、反、补码一样。负数的反码为除符号位外按位取反,补码为反码加 1.
为什么 C 系语言中,int 的范围为$-2^{n-1} \sim 2^{n-1} - 1$,其中 n 为 bit 数。同样以 1 byte 来算,8bit 能够表达的有符号范围如下:
1111 1111 # -(2^7 - 1) = -127
0111 1111 # (2^7 - 1) = 127
但从排列组合来看,8 位能够表达的数字共有$2^8 = 256$个,而$-127 \sim 127$只有 255 个数。还有一个数跑哪去了呢。答案是 0,0 有两种表示方法:
1000 0000 # -0
0000 0000 # +0
这无疑是一种浪费,我们选择下面那个数表示 0。而将上面的 1000 0000(首先它表示一个负数,负数用补码表示,因此它应该是代表 -128 的补码)用于表示 -128,如此一来,8bit 的表达范围扩展到$-128 \sim 127$,正好$256=2^8$个数。
至于为什么用 1000 0000 来表示 -128,此中还是有些门道,来看 -127 的三码:
1111 1111 # -127 原码
1000 0000 # -127 反码
1000 0001 # -127 补码
如此一来 -128 的补码就等于 -127 的补码减一得:1000 0000. 关于负数为什么用补码表示,其实是为了做加法,计算机内没有减法器,只有加法器,具体参见上文。以上我只是用 8bit 数举例,实际计算机中 int 可能有 32bit,以此类推。
以下部分转载自:http://www.cnblogs.com/junsky/archive/2009/08/06/1540727.html
假设有一个 int 类型的数,值为 5,那么,我们知道它在计算机中表示为:
00000000 00000000 00000000 00000101
5 转换成二制是 101,不过 int 类型的数占用 4 字节(32 位),所以前面填了一堆 0。
1 byte = 8 bits
现在想知道,-5 在计算机中如何表示?**在计算机中,负数以其正值的补码形式表达。**什么叫补码呢?这得从原码,反码说起。
- 原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。 比如 00000000 00000000 00000000 00000101 是 5 的原码。
- 反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。 取反操作指:原为 1,得 0;原为 0,得 1。(1 变 0; 0 变 1) 比如:将 00000000 00000000 00000000 00000101 每一位取反,得 11111111 11111111 11111111 11111010。 称: 11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。 反码是相互的,所以也可称: 11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。
- 补码:反码加 1 称为补码。 也就是说,要得到一个数的补码,先得到反码,然后将反码加上 1,所得数称为补码。 比如: 00000000 00000000 00000000 00000101 的反码是: 11111111 11111111 11111111 11111010。 那么,补码为: 11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在计算机中表达为: 11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
再举一例,我们来看整数 -1 在计算机中如何表示。假设这也是一个 int 类型,那么:
- 先取 1 的原码:
00000000 00000000 00000000 00000001
- 得反码:
11111111 11111111 11111111 11111110
- 得补码:
11111111 11111111 11111111 11111111
可见,-1 在计算机里用二进制表达就是全 1。16 进制为:0xFFFFFF.
转载部分结束,感谢原作者。为了便于阅读,我对排版稍作了改动。
常见的位运算
C/C++ 位运算符
~
:按位取反<<
:左移位>>
:右移位&
:按位与|
:按位或^
:按位异或
Summary from learncpp.com
Cf. https://www.learncpp.com/cpp-tutorial/bit-manipulation-with-bitwise-operators-and-bit-masks/
Summarizing how to set, clear, toggle, and query bit flags:
To query bit states, we use bitwise AND:
|
|
To set bits (turn on), we use bitwise OR:
|
|
To clear bits (turn off), we use bitwise AND with bitwise NOT:
|
|
To flip bit states, we use bitwise XOR:
|
|
Applications
除以/乘以 2
n >> 1
: 右移一位,低位的 1 被忽略,floor(n/2).
n << 1
: 左移一位,低位补 0,n * 2.
取相反数
~n + 1
:对 n 取反得到反码,加 1 得到补码,即对应的负数。
(n ^ -1) + 1
:-1 的二进制全是 1,按位异或之后得到的等价与按位取反。
计算 n+1
-(~n)
:
~n + 1 = -n
-(~n + 1) = n
-~n - 1 = n
-~n = n+1
计算 n-1
~-n
:
n-1 = -(-n+1)
-n+1 = -(~-n) <--- n+1 = -~n
n-1 = ~-n
if(x==a) x=b; if(x==b) x=a;
x = a ^ b ^ x
:如果 x=a,则相当于对 b 翻转两次,结果自然是 b;如果 x=b,那么 b^b=0,0 异或任何数都没有效果,故结果是 a.
判断奇偶
n & 1
: 如果结果为 1,则表明 n 的最低位为 1,n 为奇数;反之为偶数。
变量互换(无需临时变量)
a ^= b ^= a ^= b
:
|
|
判断是否为 2 的幂
n & (n-1)
:如果 n 是 2 的幂,则
|
|
对 2 的 n 次方取余
m & ((1<<n) - 1)
:
|
|
取 n 的第 m 低位
(n >> (m-1)) & 1
: 右移 m-1 位,最低位即为原从低到高第 m 位
将 n 的第 m 低位置 1
n | (1 << (m-1))
: 将 1 左移 m-1 位于 n 的第 m 低位对应,相或置 1
将 n 的第 m 低位置 0
n & ~(1 << (m-1))
: 将 1 左移 m-1 位对上 n 的第 m 低位,按位取反相与