1 原码、反码与补码
1.1 概述
✈️在计算机的运算中,都是以补码的方式进行运算的,当我们要查看运算结果的时候需要看原码。(运算过程用补码,查看结果用原码)
原码 :最高位符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制。
反码:正数的反码与原码一致,负数的反码是对原码按位取反,只是最高位(符号位)不变。
补码:正数的补码与原码一致,负数的补码是该数的反码加1。
1.2 规律及方法
🍎由于Java中无无符号数,因此这里讨论有符号数,对于有符号的而言:
⭐️ 二进制的最高位是符号位,0表示正数,1表示负数;
⭐️ 正数的原码、反码、补码都一样;
⭐️ 负数的反码 = 除符号位不变以外其余位取反(0变1,1变0);
⭐️ 负数的补码 = 该数的反码 + 1;
⭐️ 0的反码、补码都是0。
2 位运算符
2.1 位运算符概述
🍎 位运算符一览表:
符号 | 名称 | 运算 |
& | 按位与 | 两位全为1,结果为1,否则为0 |
/(竖线) | 按位或 | 两位有一个为1,结果为1,否则为0 |
^ | 按位异或 | 两位一0一1则为1,反之为0 |
~ | 按位取反 | 0变1,1变0 |
>> | 算术右移 | 低位溢出,符号位不变,并用符号位补溢出的高位 |
<< | 算术左移 | 符号位不变,低位补0 |
>>> | 逻辑右移 / 无符号右移 | 低位溢出,高位补0 |
2.2 计算 2 & 3
🍎【推导】2 & 3 = 2
由于计算机是以补码的方式进行运算,所以在计算 2 & 3 时:
⭐️先得到 2 的原码,在Java 中 int 占4个字节,一个字节为8位,则 2 的补码为 00000000 00000000 00000000 00000010;
⭐️求出 2 的补码 :由于 2 是正数,则其 原码、补码、反码都是一致的,因此其补码为 00000000 00000000 00000000 00000010;
⭐️ 同理,我们可以求得 3 的补码 = 原码 为 00000000 00000000 00000000 00000011;
⭐️ 按位 & 后求得补码为 00000000 00000000 00000000 00000010;
⭐️ 前面提到,我们查看结果需要求原码,由补码求得原码为:00000000 00000000 00000000 00000010;
⭐️ 最终求得的原码转成十进制,答案为2。
🌟【结果验证】
System.out.println(2 & 3); // 2 1
2.3 计算 ~-2
🍎【推导】~-2 = 1
1.⭐️ 先得到 -2 的原码(负数最高位为1): 10000000 00000000 00000000 00000010;
2.⭐️ 求 -2 的反码(除符号位,其余位取反): 11111111 11111111 11111111 11111101;
3.⭐️ 求 -2 的补码(补码 = 反码 + 1):11111111 11111111 11111111 11111110;
4.⭐️ 进行 ~ 取非运算:00000000 00000000 00000000 000000001;
5.⭐️ 由于取非操作后,最高位为0,表示正数,因此,该码即为运算结果的原码,对应转化成十进制数为1,故答案为1。
🌟【结果验证】
System.out.println(~-2); // 1 1
2.4 计算 ~2
🍎【推导】~2 = -3
1.⭐️ 先得到 2 的补码(正数补码原码反码相同): 00000000 00000000 00000000 00000010;
2.⭐️ 进行 ~ 取非运算:11111111 11111111 11111111 111111101;
3.⭐️ 由于取非操作后,最高位为1,表示负数,因此,需要根据补码求对应的原码,在负数中,由于 补码 = 反码 + 1,我们可以求得反码(补码-1):11111111 11111111 11111111 111111100;
4.⭐️ 原码求得(符号位不变,其余位取反):10000000 00000000 00000000 00000011;
5.⭐️ 将原码转成十进制为 2,因此,答案为-3。
🌟【结果验证】
System.out.println(~2); // -3 1
2.5 计算 1 >> 2
🍎 【推导】1 >> 2 = 0
✈️【本质】实际就是进行了 1 / 2 / 2 = 0的运算
1.⭐️ 1 对应的补码为 00000000 00000000 00000000 00000001;
2.⭐️ 进行 >> 运算,右移两位,低位溢出,高位用符号位补,得到: 00000000 00000000 00000000 00000000;
3.⭐️ 该补码对应的原码不变,转成十进制为0。
🌟【结果验证】
System.out.println(1 >> 2); // 0 1
2.6 计算 1 << 2
🍎 【推导】1 << 2 = 4
✈️【本质】实际就是进行了 1 * 2 * 2 = 4的运算
1.⭐️ 1 对应的补码为 00000000 00000000 00000000 00000001;
2.⭐️ 进行 << 运算,左移两位,符号位不变,低位补0,得到: 00000000 00000000 00000000 00000100;
3.⭐️ 该补码对应的原码不变,转成十进制为4。
🌟【结果验证】
System.out.println(1 << 2); // 4 1
3 补充:为什么计算机采用补码运算的方式
😎对于有符号数,内存要区分符号位和数值位,要是能把符号位和数值位等同起来,让它们一起参与运算,不再加以区分,只用加法器就可以同时实现加法和减法运算,这样硬件电路就变得简单了。
8 - 3 等价于 8 + (-3),12 - (-9) 等价于 10 + 9。
简化硬件电路的代价就是有符号数在存储和读取时都要进行转化。这个转换过程就涉及到我们熟悉的原码、反码、补码。
原码将一个整数转换成二进制形式,就是其原码。例如short a = 5;,a 的原码就是0000 0000 0000 0101;更改 a 的值a = -19;,此时 a 的原码就是1000 0000 0001 0011。
❤️ 通俗的理解,原码就是一个整数本来的二进制形式。
正数与负数的反码不一样。
对于正数,它的反码就是其原码(原码和反码相同);负数的反码是将原码中除符号位以外的所有位(数值位)取反,也就是 0 变成 1,1 变成 0。例如 short a = 5;,a 的原码和反码都是 0000 0000 0000 0101;更改 a 的值 a = -19;,此时 a 的反码是 1111 1111 1110 1100。
❓❓❓为什么需要反码?
答:反码的作用就相当于数学中的负数,有了负数,才可以实现减法与加法运算统一成加法运算。
❓❓❓有了反码为什么还需要补码?
答:因为 “0” 这个特殊数字的存在。
将减法运算按加法运算处理,负数需要用反码表示,那么用 8 位二进制反码表示的正数范围:+0 —— +127;负数范围:-127 —— -0。但是,其中有两个特殊的编码会出现:
[0_0000000]=+0 (反码)
[1_1111111]=-0 (反码)
+0 和 -0 代表的都是 0。这样一来,“0” 这个数字在计算机中的编码就不是唯一的了。对于计算机来说,这是绝对不行的,因为任何数字都只能有 1 个编码。
我们知道 0 既不是正数也不是负数,为了解决这个编码不唯一的问题,把 0 当成正数,也即 +0,这样 0 的编码就变成:0_0000000。那 8 位二进制表示的正数范围仍然是:+0 —— +127。负数整体向后“挪动1位”,反码 +1,{1_1111111}编码就不再表示 -0,而变成了 -1。顺着推,最小的编码{1_0000000}就是 -128,8 位二进制表示的负数范围从:-127 —— -0 变成:-128 —— -1,就能成功解决问题。(不要忽略了看结果需要转换成原码哦!!!)