位运算
1.位运算与移位运算
细化 | 符号 | 描述 | 运算规则 |
---|---|---|---|
按位运算 | & | 与 | 两位都为1,那么结果为1 |
| | 或 | 有一位为1,那么结果为1 | |
~ | 非 | ~0 = 1,~1 = 0 | |
^ | 异或 | 两位不相同,结果为1 | |
移位运算 | << | 左移 | 各二进制位全部左移N位,高位丢弃,低位补0 |
>> | 右移 | 各二进制位全部右移N位,若值为正,则在高位插入 0,若值为负,则在高位插入 1 | |
>>> | 无符号右移 | 各二进制位全部右移N位,无论正负,都在高位插入0 |
2.原码、反码和补码
-
原码:原码表示法在数字前面增加了一位符号位,即最高位为符号位,正数位该位为0,负数位该位为1.比如十进制的5如果用8个二进制位来表示就是00000101,-5就是10000101。
-
反码:正数的反码是其本身,负数的反码在其原码的基础上,符号位不变,其余各个位取反。5的反码就是00000101,而-5的则为11111010。
-
补码:正数的补码是其本身,负数的补码在其原码的基础上,符号位不变,其余各位取反,最后+1。即在反码的基础上+1。5的反码就是00000101,而-5的则为11111011。
3.位运算的应用
1.不用额外的变量实现两个数字互换
-
通过三次异或操作完成了两个变量值的替换。
0^a = a,a^a = 0;
a ^ b = b ^ a;
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
a ^ b ^ a = b;
假设a,b两个变量,经过如下步骤完成值交换:a=a^b ,b=b^a ,a=a^b。
证明如下:
因为a ^ b = b ^ a,又a=a^b ,b=b^a。 故b=b^a= b^ (a^b)=a。
继续a=a^b, a=(a^b) ^ b^ (a^b),故a=b。完成值交换。
public void swapTest(){
int a = 1, b =2;
System.out.println(a+" "+b);//1 2
a = a^b;
b = b^a;
a = a^b;
System.out.println(a+" "+b);//2 1
}
2.不用判断语句实现求绝对值
-
return (a^(a>>31))-(a>>31); 或 return (1 - ((a >>> 31) << 1)) * a;
若a为正数,则不变,需要用异或0保持的特点;
若a为负数,则其补码为原码翻转每一位后+1,先求其原码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。
那么综合上面步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a^0=a,不变;若a为负数,a>>31等于-1 ,a^-1翻转每一位。
public static void absTest(){
int a = -123;
System.out.println((a ^ (a >> 31)) - (a >> 31));//123
System.out.println((1 - ((a >>> 31) << 1)) * a);//123
}
3. 判断一个数的奇偶性
-
通过与运算判断奇偶数,伪代码如下:
n&1 == 1?”奇数”:”偶数” 即判断最低位是不是0
奇数最低位肯定是1,而1的二进制最低位也是1,其他位都是0,所以所有奇数和1与运算结果肯定是1。
public static void main(String[] args) {
int a = 1,b = 2;
System.out.println(evenOrOdd(a));//true
System.out.println(evenOrOdd(b));//false
}
public static boolean evenOrOdd(int n){
return (n & 1) == 1? true : false;//奇数返回true,偶数返回false
}
4.整数的平均值
- 对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的。return (x&y)+((x^y)>>1); 或者 return (x +y ) >> 1;
public static void main(String[] args) {
int x = Integer.MAX_VALUE, y = Integer.MAX_VALUE;
System.out.println((x & y) + ((x ^ y) >> 1));//2147483647
System.out.println((x + y) / 2);//-1 溢出
}
5.判断数中有多少个1
public static void main(String[] args) {
int a = 7;
int count=0;
while(a!=0){
if((a>>1)<<1!=a){ //对a进行"右移再左移",判断其结果是否等于原来的a
++count;
}
a = a>>>1;//完成1次判断,对a进行无符号右移并把运算结果赋值给a自身
}
System.out.println("a转换为二进制数后,有"+count+"个1");
}
6.判断一个数是不是2的幂
- return ((x&(x-1))==0)&&(x!=0);x中只有一个1
7.获取int最大最小值
-
最大return ( 1 << 31 ) - 1; 或者 return ~ ( 1 << 31 ) ;
-
最小return 1 << 31; 或者 return 1 << - 1;
8.获取long最大最小值
- 最大 return ( ( long ) 1 << 127 ) - 1;
- 最小 return 1 << 127;
9.乘2除2,乘2的m次方除以2的m次方
- return n << 1;
- return n >> 1;
- return n <<m;
- return n>>m;
10.取两个数的最大值
- return b & ( (a -b ) >> 31 ) | a & (~ (a -b ) >> 31 ); 如果a>=b,(a-b)>>31为0,否则为-1
11.取两个数的最小值
- return a & ( (a -b ) >> 31 ) | b & (~ (a -b ) >> 31 ); 如果a>=b,(a-b)>>31为0,否则为-1
12.判断符号是否相同
- return (x ^ y ) > 0; 即判断最高位(符号位)是否相同
13.从低位到高位,取n的第m位
- return (n >> (m - 1 ) ) & 1;
14.从低位到高位.将n的第m位置1
- return n | ( 1 << (m - 1 ) ); 将1左移m-1位找到第m位,得到000…1…000 n在和这个数做或运算
15.从低位到高位,将n的第m位置0
- return n & ~ ( 1 << (m - 1 ) ); 将1左移m-1位找到第m位,取反后变成111…0…1111 n再和这个数做与运算