文章目录
一、进制表示
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 右移四位,再做或运算……
来源:CSDN
作者:weixin_45594025
链接:https://blog.csdn.net/weixin_45594025/article/details/104430714