文章目录
一、位运算概述
计算机对于计算数据的说话,都是以二进制来进行运算的,所以使用位运算相比直接使用(+、-、 *、/)运算符,要更高效,能显著调高代码在计算机中的执行效率。
二、位运算概览
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 非,取反 | 0变1,1变0 |
<< | 左移 | 不分正负,低位补0 |
>> | 右移 | 用符号位填充高位 |
>>> | 无符号右移,逻辑右移 | 用0填充高位 |
a | b | ~a | a&b | a|b | a^b |
---|---|---|---|---|---|
1 | 1 | 0 | 1 | 1 | 0 |
0 | 1 | 1 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 0 | 0 |
异或
异或,可以理解为不进位加法:1+1=0,0+0=0,1+0=1
性质:
- 交换律 可任意交换运算因子的位置,结果不变
- 结合律 (即(ab)c==a(bc))
- 对于任何数x,都有xx=0,x0=x,同自己求异或为0,同0求异或为自己
- 自反性 ABB = A^0 = A,连续和同一个因子做异或运算,最终结果为自己
二进制数的原码、反码、补码
-
正数的原码、反码、补码都是一致的
-
负数的反码,除了符号位与原码一致,其余位都与原码相反
-
负数的补码 = 反码 + 1
数据存储和运算都是以补码的方式进行的
左移 <<
左移使,不管正负,低位补0
正数 a = 20 << 2
20的二进制补码:0001 0100
向左移动两位后: 0101 0000
结果:a = 80
负数 a = -20 << 2
-20的二进制原码:1001 0100
-20的二进制反码:1110 1011
-20的二进制补码:1110 1100
左移两位后的补码: 1011 0000
反码:1010 1111
原码:1101 0000
结果:a = -80
右移 >>
如果该数为正,则高位补0,若为负数,则高位补1
正数 a = 20 >> 2
20的二进制补码:0001 0100
向右移动两位后:0000 0101
结果:a = 5
负数 a = -20 >>2
-20的二进制补码:1110 1100
向右移动两位后:1111 1011
反码:1111 1010
原码:1000 0101
结果:a = -5
无符号右移 >>>
无符号右移>>> 也叫逻辑右移,若该数位正数,则高位补0,若该数为负数,则右移后高位同样补0
正数 a = 20 >>>2 与 a = 20 >> 2相同
结果:a = 5
负数 a = -20 >>> 2
(int类型)
-20的二进制原码:10000000 00000000 00000000 00010100
反码:11111111 11111111 11111111 11101011
补码:11111111 11111111 11111111 11101100
右移:00111111 11111111 11111111 11111011
结果:a = 1073741819
三、位运算的使用奇巧淫技
判断奇偶数
int num = 101;
System.out.println(num +"是"+((num&1)==0?"偶数":"奇数"));
5的二进制代码为101,5与1运算就是 101与001做&运算。
我们通过二进制判断奇偶数的话,看的是二进制最后一位,如果最后一位为0的话是偶数,为1的话是奇数
为什么?因为二进制除了最后以为,其他位都是2的幂次方,必然是偶数,所以我们通过判断最后一位是0或者1 就可以判断是奇数还是偶数。
获取二进制位是1还是0
int n = 86;
System.out.println( n+"的第五位是"+ (((n&(1<<4))>>4)==0?"0":"1") );
System.out.println( n+"的第五位是"+ (((n>>4)&1)==0?"0":"1") );
交换两个整数变量的值
int num1 = 10;
int num2 = 20;
num1 = num1^num2;
num2 = num1^num2;
num1 = num1^num2;
System.out.println("num1="+num1+", num2="+num2);
不用判断语句,求整数的绝对值
int a = -10;
int b = ((a >> 31) ^ a) + (a >>> 31);
System.out.println("a="+a+", b="+b);
整数取绝对值
搞了一整天,,取绝对值的还是懵懵的
先来分析一下 a = -86:
原码:1000 0000 0000 0000 0000 0000 0101 0110
反码:1111 1111 1111 1111 1111 1111 1010 1001
补码:1111 1111 1111 1111 1111 1111 1010 1010
取反:0000 0000 0000 0000 0000 0000 0101 0101
(+1): 0000 0000 0000 0000 0000 0000 0101 0110
由此可以得出,负数的绝对值为,补码取反+1,即 ~a+1。
int a = -86;
int b = ~a + 1;
System.out.println(b);
但是,这个还不能达到题目所要求的,不用判断,当假定 a = -86的时候,就已经知到 a 是负数了。
分析:任何一个数 异或0 结果为其本身,与 -1 (二进制全为1) 相当于取反
一个32 bit的int类型的正数 >> 31,结果为0,负数 >> 31 为-1。
即 (a>>31)的结果为 0 或者 -1。如果 a 为负数,(a>>31)^a 相当于 ~a,则需要 + 1 才能得到 a 的绝对值
所以代码为:((a >> 31) ^ a) + (a >>> 31)
- 记得 a 是以补码的方式进行计算的,负数的补码不等于原码,计算前先转换!
或:
int a = -86;
int b = a >> 31;
// 当a为正时:b = 0,a ^ b = a;
// 当a为负时:b = -1,a ^ b = ~a;
return (a ^ b) - b;// 也可以写为:(a ^ (a >> 31)) - (a >> 31)