位运算

时光总嘲笑我的痴心妄想 提交于 2020-02-21 22:23:12

一、进制表示

1、进制

进制是一种记数方式 ,可以用有限的数字符号代表所有的数值。由特定的数值组成。

2、进制的表现形式

  • 二进制: 由 0 和 1 两个数字组成,都是以 0b 开始;
  • 八进制: 由 0~7 数字组成,为了区分于其他进制的数字,都是以 0 开始;
  • 十进制: 都是以 0~9 这九个数字组成,不能以 0 开头;
  • 十六进制:由 0 ~ 9 和 A~ F 组成,为了区分于其他进制的数字,都是以 0x 或 0X 开始。

3、原码、反码和补码

原码——即为计算机中对数值的二进制表示。如,十进制的 5 用二进制表示为 0000 0101 ;(最高位为符号位,1 表示负,0 表示正,并且在原、反、补的转换中,符号位不变)

反码——即取反,对于正数来说,反码与原码相同;对于负数来说,反码为原码的各位取反,符号位不变。如 0011 0111 的反码 = 0011 0111,1101 0010 的反码 = 1010 1101 ;

补码——计算机中,数值一律用补码表示和存储的,正数的补码与原码相同,负数的补码为“其反码 + 1”,如 0101 1101的补码 = 0101 1101 ,1101 0010 的补码 = 1010 1110。(补码求原码:就是对其补码继续求补码)

二、位运算

注意:
计算机中所有参与运算的都是以补码形式进行的,结果也是补码,因此也需要将补码转换成为原码的形式存在;不同长度的数据进行位运算:如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。

1、&——与运算(and)

(1)运算规则

两位同时为 1,结果才为 1,否则结果为 0。

例一: 5 & 6

5	原码:0000 0101
6	原码:0000 0110 	
	与结果:0000 0100 	
	十进制结果:4

例二: 5 & -6

5	原码:0000 0101
-6	原码:1000 0110 	
	反码:1111 1001 	
	补码:1111 1010 	
	与结果:0000 0000 	
	十进制结果:0

(2)应用

应用一:清零

如果想将一个单元清零,只要与一个各位都为零的数值相与,则结果为零。

应用二:取一个数的指定位

比如,取 X = 1010 1110 的低 4 位,另找一个数 Y,令 Y 的低4位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行按位与运算(X & Y = 0000 1110)即可得到 X 的低 4 位。

应用三——判断奇偶

只要根据最未位是 0 还是 1 来决定,为 0 就是偶数,为 1 就是奇数。因此可以用 if ((a & 1) == 0) 代替 if (a % 2 == 0)来判断a是不是偶数。

2、|——或运算(or)

(1)运算规则

两位同时为 0,结果才为 0,否则结果为 1。

例一: 5 | 6

5	原码:0000 0101
6	原码:0000 0110 	
	或结果:0000 0111 	
	十进制结果:7

例二: 5 | -6

5	原码:0000 0101
-6	原码:1000 0110 
	反码:1111 1001 	
	补码:1111 1010 	
	或结果:1111 1111——>反码:1000 0000——>原码:1000 0001 	
	十进制结果:-1

(2)应用

对一个数的某些位置 1:

比如将数 X = 1010 1110 的低 4 位设置为 1,只需要另找一个数 Y,令 Y 的低 4 位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行按位或运算(X|Y=1010 1111)即可得到。

3、^——异或运算 (xor)

(1)运算规则

参加运算的两个对象,两个相应位相同则结果为 0,相应位相异则结果为 1。

异或运算的特点:

  • 两个相同的数异或之后结果会等于 0,即 n ^ n = 0;
  • 任何数与 0 异或等于它本身,即 n ^ 0 = n;
  • 异或运算支持交换律和结合律。

例一: 5 ^ 6

5	原码:0000 0101
6	原码:0000 0110 	
	异或结果:0000 0011 
	十进制结果:3

例二: 5 ^ -6

5	原码:0000 0101
-6	原码:1000 0110
 	反码:1111 1001
 	补码:1111 1010 	
 	异或结果:1111 1111——>反码:1000 0000——>原码:1000 0001 	
 	十进制结果:-1

注意:

这里的 ^ 符号与我们平时用来做乘幂的 ^ 不同,java中不用 ^ 来做幂运算,java中做幂运算有数学函数 Math.pow(x,a) ,表示 x 的 a 次方。

(2)应用

翻转指定位:

比如将数 X=1010 1110 的低 4 位进行翻转,只需要另找一个数Y,令 Y 的低 4 位为 1,其余位为 0,即 Y = 0000 1111,然后将 X 与 Y 进行异或运算(X^Y=1010 0001)即可得到。

4、~——非运算(not)

(1)运算规则

对一个二进制数按位取反,即将 0 变 1,1 变 0。

例一: ~5

5	原码:0000 0101 	
	非结果:1111 1010——>反码是:1000 0101——>原码是:1000 0110 	
	十进制结果:-6

例二: ~-5

-5	原码:1000 0101 	
	反码:1111 1010 	
	补码:1111 1011 	
	非结果:0000 0100 	
	十进制结果:4

三、移位运算

1、<<——左移运算

左移运算,将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

例:设 a = 1010 1110,a = a<< 2 将 a 的二进制位左移 2 位、右边补 0,即得 a = 1011 1000。(注:若左移时舍弃的高位不包含 1,则每左移一位,相当于该数乘以 2)

2、>>——右移运算

右移运算符,区别于“无符号右移运算符”,将一个运算对象的各二进制位全部右移若干位,使用符号拓展机制,如果值为正,则在高位补0,如果值为负,则在高位补1。(注:每右移一位,相当于该数除以2)

3、>>>——无符号右移运算(没有无符号左移运算)

“无符号”右移运算符,将一个运算对象的各二进制位全部右移若干位,使用 0 拓展机制,无论值为正、负,都在高位补 0。

下面是对上述运算符的一段测试代码:

public class BitOperation {
	
	public static void main(String[] args) {
		//System.out.println(Integer.toBinaryString(9));
		//System.out.println(0b101);
		System.out.println(5 & 6);// 4
		System.out.println(5 & -6);// 0
		System.out.println(5 | 6);// 7
		System.out.println(5 | -6);// -1
		System.out.println(5 ^ 6);// 3
		System.out.println(5 ^ -6);// -1
		System.out.println(5 ^ 0);// 5,任何数与 0 异或等于它本身
		System.out.println(~5);// -6
		System.out.println(~-5);// 4
		
		long a = 123;
		int b = -1;
		System.out.println(a & b);// 123
		
		System.out.println(19 << 3);// 152
		System.out.println(19 >> 3);// 2
		System.out.println(-19 >> 3);// -3
		System.out.println(19 >>> 3);// 2
		System.out.println(-19 >>> 3);// 
	}

}

四、位运算应用

1、判断奇偶数(与运算)

public static void judge(int val){
	if((val & 1) == 1){// 和1进行与运算
		System.out.println(val + " is an odd number");
	}else{
		System.out.println(val + " is an even number");
	}
}

2、交换两个数(异或)

public static void exchange(int a, int b){
	System.out.println("交换前:a = " + a + ", b = " + b);
	/*
	 * 1、把(1)中的a代入(2)中的a,则有
	 * 		b = a ^ b = (a ^ b) ^ b = a ^ b ^ b = a ^ 0 = a
	 * 		a的值成功赋给b;
	 * 2、把(2)中的b代入(3)中的b,则有
	 * 		a = a ^ b = a ^ (a ^ b) = a ^ a ^ b = 0 ^ b = b
	 * 		b的值成功赋给a。
	 */
	a = a ^ b;// (1)
	b = a ^ b;// (2)
	a = a ^ b;// (3)
	System.out.println("交换后:a = " + a + ", b = " + b);
}

3、求 m 的 n 次方

(1)可以使用系统自带的 pow 函数, java.lang.Math.pow(double a, double b);

(2)可以让 n 个 m 相乘。如下:(时间复杂度为 O(n))

int pow(int n){
    int tmp = 1;
    for(int i = 1; i <= n; i++) {
        tmp = tmp * m;
    }
    return tmp;
}

(3)位运算

举例(注意:这里的 ^ 表示幂):n = 13,则 n 的二进制数表示为 1101,也可表示为 2 ^ 3 + 2 ^ 2 + 2 ^ 0,那么 m 的 13 次方可以拆解为:m ^ 13 = m ^ (8 + 4 + 1),即 m ^ 1101 = m ^ 1000 * m ^ 0100 * m ^ 0001。可以通过 & 1和 >>1 来逐位读取 1101,若当前位为 1,则将该位代表的乘数累乘到最终结果。代码如下:

public static int pow(int m, int n){// m为底,n为幂
	int product = 1;
	int temp = m;
	while(n != 0){
		if((n & 1) == 1){ //n的当前位为1,该位代表的乘数要累乘到最终结果中
			product *= temp;
		}
		temp *= temp;// 每读取n的一位,temp用来记录该位应该乘的m的个数
		n = n >> 1;
	}
	return product;
}

时间复杂度为 O(logn),代码运行过程如下:
在这里插入图片描述

4、找出不大于 N 的最大的 2 的幂指数

(1)一种方法就是让 1 不断乘以 2,时间复杂度是 O(logn),代码如下:

public int findN(int N){
    int sum = 1;
    while(true){
        if(sum * 2 > N){
            return sum;
        }
        sum = sum * 2;
    }
}

(2)位运算:

假设 N = 19,分析如下:

进行位运算,先把数转换成二进制,19 转换成 0001 0011(这里采用八位的二进制数)。我们的目标即为,把二进制数中最左边的 1 保留,右边的 1 全部变为 0 之后的数,即 0001 0000。解法如下:

  • 找到最左边的 1,然后把它右边的所有 0 变成 1,即 0001 0011 变为 0001 1111;
  • 给上一步得到的数值加 1,即 0001 1111 + 1 = 0010 0000;
  • 把上一步得到的数再向右移动一位,0010 0000 >> 1 = 0001 0000,最终结果即为 0001 0000。

代码如下:

public static int findN2(int n){
	n |= n >> 1;
	n |= n >> 2;
	n |= n >> 4;
	n |= n >> 8;// 整型一般是32位,
	return (n + 1) >> 1;
}

通过对 n 进行右移和或运算即可得到结果,时间复杂度为 O(1)。

运行过程如下:

假设最左边的 1 处于二进制数中的第 k 位(从左往右数),则当 n 右移一位之后,得到的结果中第(k + 1)位也是 1,然后把 n 与右移一位得到的结果做或运算,得到的结果中第 k 位和第 (k + 1)位都为 1;同理,再次把 n 右移两位后,得到的结果中第(k + 2)位和第 (k + 3)位也都为 1,再次做或运算,得到的结果中第 k、k + 1、k + 2 和 k + 3 都是 1;再将 n 右移四位,再做或运算……

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!