位运算总结
来源:互联网 发布:手机通话变声器软件 编辑:程序博客网 时间:2024/06/14 04:35
位运算总结
位运算,相比普通的代码最大的优点就是其带来的高效性,也因此可以常在底层源码中看见它们的踪影。
本文就位运算常见的操作作一个总结,若您另有关于位运算巧妙的运用可以于底部留言区留言。
首先还是先来回顾下位操作的基础知识。(除非特别说明,否则以下都以 2 进制为例)
相关基础
1. 与运算
与运算符 "&" 是双目运算符。只有对应的两个二进位均为 1 时,结果位才为 1,否则为 0。例如:
9 & 5 = 00001001 & 00000101 = 00000001 = 1
2. 或运算
或运算符 "|" 是双目运算符。只要对应的两个二进位有一个为 1 时,结果位就为 1。例如:
9 | 5 = 00001001 | 00000101 = 00001101 = 13
3. 非运算
非运算符 "~" 为单目运算符。其功能是对参与运算的各二进位求反。例如:
~ 9 = ~ 00001001 = 11110110 = -10
4. 异或运算
异或运算符 "^" 是双目运算符。其功能是对参与运算的二进位相异或,即当两二进位相异时,结果为 1,相同就为 0。例如:
9 ^ 5 = 00001001 ^ 00000101 = 00001100 = 12
5. 左移和右移
左移动就是向左移动 k 位,丢弃最高的 k 位,并在右端补 k 个 0,也就是常说的当前值乘以 2 的 k 次方。
右移动的原理也是相同的,右移 k 位就是当前数除以 2 的 k 次方。唯一不同的是分为逻辑右移和算术右移。
逻辑右移就是无符号移位,右移几位,就在左端补几个 0。
算术右移动是有符号移位,和逻辑右移不同的是,算术右移是在左端补 k 个最高有效位的值,如此看着有些奇特,但对有符号整数数据的运算非常有用。我们知道有符号的数,首位字节,是用来表示数字的正负(1 为负)。负数采用补码形式来存储,比如 - 26(11100110),算术右移 1 位之后 - 13(11110011)。如若不是补最高有效位的值 1 而是补作 0 的话,右移之后就变成正数了。
经典应用
1. i+(~i)=-1
i 取反再与 i 相加,相当于把所有二进制位设为 1,其十进制结果为 - 1。
2. 计算 n+1 与 n-1
-~n == n + 1
,~n 为其取反,负号 '-' 再对其取反并加 1。
~-n == n - 1
,思路就是找到最低位的第一个 1,对其取反并把该位后的所有位也取反,即01001000
变为01000111
。
3. 取相反数
思路就是取反并加 1,也即~n + 1
或者(n ^ -1) + 1
。
4. if(x == a) x = b; if(x == b) x = a;
利用 ^ 运算符的性质,即得x = a ^ b ^ x
。
5. n 倍数补全
当 n 为 2 的幂时,(x + n - 1) & ~(n - 1)
会找到第一个大于 x 的数,且它正好是 n 的整数倍。
6. 求二进制中 1 的个数
/* Version 1 */int count_1_bits(int n){ int count = 0; while (n) { count++; n = n & (n - 1); } return count;}/* Version 2 */int count_1_bits(int n){ x = (x & 0x55555555) + ((x >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f); x = (x & 0x00ff00ff) + ((x >> 8) & 0x00ff00ff); x = (x & 0x0000ffff) + ((x >> 16) & 0x0000ffff); return x;}
关于第二个版本,分析如下:(摘自 Matrix67 - 位运算,并作稍微修改)
以十进制数 211 为例,其二进制为 11010011,
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | <— 原数+---+---+---+---+---+---+---+---+| 1 0 | 0 1 | 0 0 | 1 0 | <— 第一次运算后+-------+-------+-------+-------+| 0 0 1 1 | 0 0 1 0 | <— 第二次运算后+---------------+---------------+| 0 0 0 0 0 1 0 1 | <— 第三次运算后,得数为 5+-------------------------------+
整个程序是一个分治的思想。第一次我们把每相邻的两位加起来,得到每两位里 1 的个数,比如前两位 10 就表示原数的前两位有 2 个 1。第二次我们继续两两相加,10+01=11,00+10=10,得到的结果是 00110010,它表示原数前 4 位有 3 个 1,末 4 位有 2 个 1。最后一次我们把 0011 和 0010 加起来,得到的就是整个二进制中 1 的个数。
7. 判断二进制中 1 的奇偶性
x = x ^ (x >> 1);x = x ^ (x >> 2);x = x ^ (x >> 4);x = x ^ (x >> 8);x = x ^ (x >> 16);cout << (x & 1) << endl; // 输出 1 为奇数
以下分析摘自 Matrix67 - 位运算,并作稍微修改,
以十进制数 1314520 为例,其二进制为 0001 0100 0000 1110 1101 1000。
第一次异或操作的结果如下:
0001 0100 0000 1110 1101 1000^ 0000 1010 0000 0111 0110 1100= 0001 1110 0000 1001 1011 0100
得到的结果是一个新的二进制数,其中右起第 i 位上的数表示原数中第 i 和 i+1 位上有奇数个 1 还是偶数个 1。比如,最右边那个 0 表示原数末两位有偶数个 1,右起第 3 位上的 1 就表示原数的这个位置和前一个位置中有奇数个 1。
对这个数进行第二次异或的结果如下:
0001 1110 0000 1001 1011 0100^ 0000 0111 1000 0010 0110 1101= 0001 1001 1000 1011 1101 1001
结果里的每个 1 表示原数的该位置及其前面三个位置中共有奇数个 1,每个 0 就表示原数对应的四个位置上共偶数个 1。
一直做到第五次异或结束后,得到的二进制数的最末位就表示整个 32 位数里 1 的奇偶性。
8. 判断奇偶性
/* 判断是否是奇数 */bool is_odd(int n){ return (n & 1 == 1);}
9. 不用临时变量交换两个数
/* 此方法对 a 和 b 相等的情况不适用 */a ^= b; b ^= a; // 相当于 b = b ^ ( a ^ b );a ^= b;
10. 取绝对值
/* 注意:以下的数字 31 是针对 int 大小为 32 而言 */int abs(int n){ return (n ^ (n >> 31)) - (n >> 31); }
其中n >> 31
取得 n 的正负号。
若 n 为正数,
n >> 31
的所有位等于 0,其值等于 0。表达式转化为n ^ 0 - 0
,等于 n;若 n 为负数,
n >> 31
的所有位等于 1,其值等于 - 1。表达式转化为(n ^ -1) + 1
,这很好理解,负数的相反数就是对其补码取反再加 1,(n ^ -1) + 1
就是在做这样的事。
11. 取两数的较大值
/* 注意:以下的数字 31 是针对 int 大小为 32 而言 */int max(int a, int b){ return (b & ((a - b) >> 31)) | (a & (~(a - b) >> 31));}
如果a >= b
,(a - b) >> 31
为 0,否则为 - 1。
12. 判断符号是否相同
/* 若 x,y 都为 0,输出真;若只有一个为 0,不会报错但运行结果是错的,因为 0 没有正负之分 */bool is_same_sign(int x, int y){ return (x ^ y) >= 0;}
13. 判断一个数是不是 2 的幂
bool is_power_of_two(int n){ return (n > 0) ? (n & (n - 1)) == 0 : false;}
如果是 2 的幂,n - 1
就是把 n 的二进制的最低的那个 1 取反为 0,并把后面的 0 全部取反为 1。
14. 取余 2 的幂次方
/* 其中 m 为 2 的幂次方,并对 m 取余 */int mod(int n, int m){ return n & (m - 1); }
参考文献
Matrix67.
Bit Twiddling Hacks.
- 位运算总结
- 常用位运算总结
- 位运算总结
- 位运算总结
- C位运算总结
- 位运算总结
- 常用位运算总结
- 位运算总结
- 位运算超强总结
- 位运算技巧总结
- 位运算总结
- 位运算要点总结
- 常用位运算总结
- 位运算总结
- 位运算总结
- 位运算总结
- 位运算总结
- 位运算总结
- 抽象类、接口
- 多态
- API
- 添加用户到本地管理员组
- Tensorflow卷积神经网络常用结构
- 位运算总结
- 反射
- C++之new、delete 与malloc、free的异同
- Seetaface 安装使用教程
- AOP和OOP区别(手动滑稽)
- linux 安装mysql
- SpringCloud 教程 | 第四篇:断路器(Hystrix)
- C# 实时折线图,波形图
- In 7-bit ZOJ