位运算Pro Max
位运算Pro Max
B1n_数字在计算机中的表示
真值
真值因为机器数第一位是符号位,所以机器数的形式值不等于真正的数值。例如上面的有符号数1000 0011,其最高位1代表负,其真正数值是-3而不是形式值131(1000 0011转换为十进制等于131)。所以将带符号位的机器数对应的真正数值称为机器数的真值。例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = -000 0001 = -1
原码
原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,比如如果是8位二进制:
1 | [+1]原 = 0000 0001 |
第一位是符号位,因为第一位是符号位,所以8位二进制数的取值范围就是:
[1111 1111,0111 1111],也即[-127,127]
反码
反码的表示方式是:正数的反码是其本身,而负数的反码是在其原码基础上,符号位不变,其余各个位取反。
例如:
1 | [+1]原 = [0000 0001] 原 = [00000001]反 |
可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值,通常要将其转换成原码再计算。
补码
在应用中,因为补码能保持加和减运算的统一,因此应用更广,其表示方式是:
- 正数的补码就是其本身;
- 负数的补码是在其原码的基础上,符号位不变,其余各个位取反,最后 + 1 (即在反码的基础上+1)对于负数,补码表示方式也是人脑无法直观看出来其数值的,通常也需要转换成原码再计算其数值。
1
2[+1]原 = [0000 0001] 原 = [00000001]反 = [00000001] 补
[-1]原 = [1000 0001] 原 = [11111110]反 = [11111111] 补
拓展:为何会有原码,补码,反码
既然原码能表示数据,为什么实际软件中更多使用的是补码呢?
现在我们知道了计算机有三种编码方式来表示一个数,编码结果如下:
1 | [+1]原 = [0000 0001] 原 = [00000001]反 = [00000001] 补 |
可以发现原码,反码和补码是完全不同的。既然原码才是被人脑直接识别并用于计算表示方式,为何还会有反码和补码呢?
首先,因为人脑可以知道第一位是符号位,在计算的时候我们会根据符号位选择对真值区域的加减。但是计算机要辨别”符号位”就必须获得全部的位的数据才可以,如果是一个32位甚至64位的以系统, 显然会让计算机的基础电路设计变得十分复杂! 于是人们想能否将符号位也参与运算的方法。
我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0,所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了。于是人们开始探索将符号位参与运算,并且只保留加法的方法。
看个例子,计算十进制表达式:1-1=0
,首先看原码表示:1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [1000 0010]原 = -2
如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的,这也是为何计算机内部不使用原码表示一个数。
为了解决原码做减法的问题就出现了反码,此时计算十进制的表达式为:1 - 1 = 0
1 | 1 - 1 = 1 + (-1) |
可以看到用反码计算减法结果的真值部分是正确的,但是“0”的表示有些奇怪
注意⚠️:取反和反码是不一样的
位运算的规则
移位运算
移位运算按照移位方向分类可以分成左移和右移,按照是否带符号分类可以分成算数移位和逻辑移位。
原始: 0000 0110 [6]
右移一次: 0000 0011 [3] 相当于除2
左移一次: 0000 1100 [12] 相当于乘2
左移运算的符号 <<,左移运算时,将全部二进制位向左移动若干位,高位丢弃,低位补0。对于左移运算,算术移位和逻辑移位是相同的。
右移运算的符号 >>,右移运算时,将全部二进制位向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:
- 算数右移时,高位补最高位;
- 逻辑右移时,高位补0。
以下例子显示移位运算的运算结果,参与运算的数字都采用由符号的8位二进制表示。
- 示例1: 29的二进制表示是0001 1101。29左移2位的结果时116,对应二进制 0111 0100;29左移3位的结果是 -24,对应的二进制是1110 1000
- 示例2: 50的二进制表示是0011 0010。50右移一位的结果是25,对应的二进制表示是 0001 1001;50右移2位的结果是12,对应的二进制表示是0000 1100。对于0和正数,算数右移和逻辑右移的结果是相同的。
- 示例3:-50的二进制表示是 11001110(补码)。-50算术右移2位的结果是-13,对应的二进制表示是1111 0011;-50逻辑右移2位的结果是51,对应的二进制表示是00110011。
计算机内部的右移运算采取的是哪一种呢?
- 对于C/C++而言,数据类型包含有符号类型和无符号类型,其中有符号类型使用关键字signed声明,无符号类型使用关键字unsigned声明,两个关键字都不使用时,默认是有符号类型。对于有符号类型,右移运算位算术右移;对于无符号类型,右移运算为逻辑右移。
- 对于Java而言,不存在无符号类型,所有的表示整数的类型都是有符号类型,因此需要区分算数右移和逻辑右移。在Java中,算数右移的符号是 >>,逻辑右移的符号是>>>。
位运算常用技巧
获取
该方法将1左移i位,得到形如 0001 0000的值。接着对这个值与num执行“位与”操作,从而将i位之外的所有位清零,最后检查该结果是否为零。不为零说明i位为1,否则i位为0。
1 | boolean getBit(int num, int i) { |
设置(将某一位设置为1)
setBit先将1左移i为,得到形如 0001 0000的值。接着对这个值与num执行“位或”操作,这样只会改变i位的数据。
这样除i位外的位均为0,故不会影响num的其余位。代码如下:
1 | boolean setBit(int num, int i) { |
清零(将某一位设置为0)
该方法与setBit相反,首先将1左移i位获得形如 0001 0000的值,对这个值取反进而得到类似1110 1111的值,接着对该值和num执行“位与”,故而不会影响到num的其余位,只会清零i位。
1 | int clearBit(int num, int i) { |
更新
这个方法是将setBit和clearBit合二为一,首先用诸如1110 1111的值将num的第i位清零。接着将待写入值v左移i位,得到一个i位为v但是其余位都为0的数。最后对之前的结果执行“位或”操作,v为1这num的i位更新为1,否则为0
1 | int clearBit(int num, int i, int v) { |
LeetCode371 位运算实现加法
给你两个整数 a 和 b ,**不使用 **运算符 + 和 - ,计算并返回两整数之和。
示例 1:
1 | 输入:a = 1, b = 2 |
示例 2:
1 | 输入:a = 2, b = 3 |
两个二进制位相加的情况:
1 | [1] 0 + 0 = 0 |
两数相加考虑的情况有两种 1. 进位部分 2. 不进位部分
不进位:
a 和 b 两个数不进位部分的情况是:相同为0,不同为1,这就是 a ^ b
进位:
对于进位,只有a和b均为1的时候才会进位,并且进位只能是1,可以想到 a & b = 1,此时位数向前进1
也就是 (a & b) << 1
思路:将a和b的和,拆分为a和b的无进位加法结果与进位结果的和
代码如下:
1 | public int getSum(int a, int b) { |