位运算及运用

341 阅读7分钟

位运算概述

计算机中所有的数据二进制的形式存储在设备中。即 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));

负数的二进制表示是其原码的补码

转换步骤

  1. 取绝对值的二进制数位得到 00000000000000000000000000000111
  2. 取反 11111111111111111111111111111000
  3. +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);

应用

法则

  1. 任何数左移(右移)32的倍数位等于该数本身。

  2. 在位移运算m<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理。

  3. x & (x - 1) 的作用是将x的二进制表示中右边第一个1置0。

  4. 左移是乘以2的幂,对应着右移则是除以2的幂。

  5. 异或遵循以下公式

  • 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);

计算两数之和

  1. 二进制各位数字相加(不考虑进位),就是异或操作
  2. 二进制各位数字相加(只考虑进位),就是按位与操作,因为进位了所有需要左移一位
  3. 与第一步结果相加。重复这个步骤,直到所有的进位为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相与得到的是其本身;
  2. |通常用于要求尽可能保留1的情况,因为只要有1,或运算的结果一定是1;
  3. ~按位取反会将符号位一同取反,得到的还是一个有符号整数;
  4. ^常用于去除重复元素的情况,和“负负得正”这种情况比较类似;
  5. <<和>>通常和上面4种位运算符搭配使用,不过要注意在Python中左移是不会发生溢出的,需要人为判断溢出。