位运算概述
计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算
原码,反码,补码
机器数
一个数在计算机中的表现形式叫做机器数,这个数有正负之分,在计算机中用一个数的最高位(符号位)用来表示它的正负,其中0表示正数,1表示负数。
例如正数7,在计算机中用一个8位的二进制数来表示,是00000111,而负数-7,则用10000111表示,这里的00000111和10000111是机器数
真数
计算机中的机器数对应的真实的值就是真数,对最高位(符号位)后面的二进制数转换成10进制,并根据最高位来确定这个数的正负。对于上面的00000111和10000111来说,对最高位后面的二进制数转换成10进制是7,在结合最高位的值,得出对应的真数分别是7和-7
原码
用第一位表示符号,其余位表示值。因为第一位是符号位,所以8位二进制数的取值范围就是:[1111_1111 , 0111_1111] 即 [-127 , 127]
反码
正数的补码反码是其本身,负数的反码是符号位保持不变,其余位取反。例如正数1的原码是[0000_0001],它的反码是是其本身[0000_0001],-1的原码是[1000_0001],其反码是[1111_1110]
补码
正数的补码是其本身,负数的补码是在其反码的基础上+1,例如正数1的原码是[0000_0001],他的补码是其本身[0000_0001],-1的补码是[1111_1111]
位的二进制表示
首先定义工具方法补齐32位
private static String toBinaryString(int i) {
String binaryString = Integer.toBinaryString(i);
StringBuilder sb = new StringBuilder(binaryString);
while (sb.length() != 32) {
sb.insert(0, "0");
}
return sb.toString();
}
正整数的二进制表示
//00000000000000000000000000000111
System.out.println(toBinaryString(7));
正整数的二进制表示是其原码
负数的二进制
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
负数的二进制表示是其原码的补码
转换步骤
- 取绝对值的二进制数位得到
00000000000000000000000000000111 - 取反
11111111111111111111111111111000 - +1
11111111111111111111111111111001
拓展:同一个数字在不同的补码表示形式中是不同的。比如-15的补码,在8位二进制中是11110001,然而在16位二进制补码表示中,就是1111111111110001
位移运算
左移
不分正负数,低位补0
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//10010000000000000000000000000000
System.out.println(toBinaryString(-7<<28));
//-1879048192
System.out.println(-7<<28);
右移
高位补符号位
System.out.println("右移====");
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//11111111111111111111111111111100
System.out.println(toBinaryString(-7>>1));
//-4
System.out.println(-7>>1);
无符号右移
也称逻辑右移,高位补0
System.out.println("无符号右移====");
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//01111111111111111111111111111100
System.out.println(toBinaryString(-7>>>1));
//2147483644
System.out.println(-7>>>1);
与、或、非、异或
按位与
只有两个操作数对应位同为1时,结果为1,其余全为0
//00000000000000000000000000000111
System.out.println(toBinaryString(7));
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//00000000000000000000000000000001
System.out.println(toBinaryString(7 & -7));
//1
System.out.println(7 & -7);
按位或
只要有一个操作数为1结果就为1
//00000000000000000000000000000111
System.out.println(toBinaryString(7));
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//11111111111111111111111111111111
System.out.println(toBinaryString(7 | -7));
//-1
System.out.println(7 | -7);
按位非
原来是0则变为1,原来是1则变为0(包含符号位)
//00000000000000000000000000000111
System.out.println(toBinaryString(7));
//11111111111111111111111111111000
System.out.println(toBinaryString(~7));
//-8
System.out.println(~7);
按位异或
不相同则为1,相同则为0
//00000000000000000000000000000111
System.out.println(toBinaryString(7));
//11111111111111111111111111111001
System.out.println(toBinaryString(-7));
//11111111111111111111111111111110
System.out.println(toBinaryString(7 ^ -7));
//-2
System.out.println(7 ^ -7);
应用
法则
-
任何数左移(右移)32的倍数位等于该数本身。
-
在位移运算m<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理。
-
x & (x - 1) 的作用是将x的二进制表示中右边第一个1置0。
-
左移是乘以2的幂,对应着右移则是除以2的幂。
-
异或遵循以下公式
- a ^ 0 = a
- a ^ a = 0
- a ^ b = b ^ a
- a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
- a ^ b ^ a = b
求整型最大值
System.out.println(-1>>>1);
System.out.println(~(1 << 31));
System.out.println((1 << -1)-1);
System.out.println(~(1 << -1));
求m*2^n
//2^3=8
System.out.println("2^3=" + (1<<3));
//3*2^3=24
System.out.println("3*2^3=" + (3<<3));
判断奇偶性
int i = 8;
System.out.println((i&1) == 1);
不使用中间值交换变量
参照上述异或遵循公式
int a = -2312313, b = -123124;
a = a ^ b;
b = a ^ b;//b = (a ^ b) ^ b
a = a ^ b;//a = (a ^ b) ^ ((a ^ b) ^ b)
System.out.println(a);
System.out.println(b);
取绝对值
int i = -278;
System.out.println((i^(i>>31))-(i>>31));
判断是否是2的整数幂
2的整数幂的二进制为 10000...(一个1后面全是0),减一后为 111...(全是1)。此时把两个按位与后若为0则是2的整数幂
int i = 16;
System.out.println((i & (i - 1)) == 0);
计算两数之和
- 二进制各位数字相加(不考虑进位),就是异或操作
- 二进制各位数字相加(只考虑进位),就是按位与操作,因为进位了所有需要左移一位
- 与第一步结果相加。重复这个步骤,直到所有的进位为0
int a = 100, b = 200;
while (b != 0) {
int sum = a ^ b;
int carr = (a & b) << 1;
a = sum;
b = carr;
}
System.out.println(a);
找出不连续数
原理:A ^ A ^ B ^ C ^ C = B
for循环中的结果为 0 ^ 1 ^ 2 ^ 3 ^ 0 ^ 1 ^ 3 ^ 4 最后再异或数组长度 ^ 4
int[] arr = {0, 1, 3, 4};
int ret = 0;
for (int i = 0; i < arr.length; i++) {
ret ^= i;
ret ^= arrs1[i];
}
//2
System.out.println(ret ^ arr.length);
找到1-n重复值
原理:A ^ A ^ B ^ C ^ C = B
int[] arr = {0, 1, 2, 3, 4, 5, 1};
int a = 0;
for (int i = 0; i < arr.length; i++) {
a ^= i;
a ^= arr[i];
}
//1
System.out.println(a ^ (arr.length - 1));
判断某个二进制位上是否为1
int i = 3;
int n = 1024000;
System.out.println((n >> i) & 1);
找出不大于N的2的最大整数幂
原理:N的最左边的二进制为1,则不断右移使所有低位的二进制位全部变为1。此时所有二进制位都为1了,再+1即可使最高位为1,其他位全为0,因为+1造成了进位,所以右移一位消除影响,得到的结果就是2的最大整数幂。
int N = 1025;
N |= N >> 1;
N |= N >> 2;
N |= N >> 4;
N |= N >> 8;
N |= N >> 16;
System.out.println(N + 1 >> 1);
总结
- &通常用于需要选择特定的数位的情况,因为任何一个数位和1相与得到的是其本身;
- |通常用于要求尽可能保留1的情况,因为只要有1,或运算的结果一定是1;
- ~按位取反会将符号位一同取反,得到的还是一个有符号整数;
- ^常用于去除重复元素的情况,和“负负得正”这种情况比较类似;
- <<和>>通常和上面4种位运算符搭配使用,不过要注意在Python中左移是不会发生溢出的,需要人为判断溢出。