原码、反码和补码是便于计算机进行运算所出现的。在我们探讨为什么会出现他们之前,首先介绍一下数字在计算机中的存储形式吧。
计算机存储数字
计算机存储的数字均为二进制;且最高一位是符号位,1代表负数,0代表正数。
举个🌰:十进制数字 77= 64+8+4+1 = 2^6+2^3+2^2+2^0 = 1001101。假设该数字用1字节存储,那么除了最高位的1比特位是区分符号的,还有7比特位存储数字。既77的“在计算机中的存储数字”为下表
| 符号位 | 数字位 |
|---|---|
| 0 | 1001101 |
最终我们把77变成了01001101,这个数字就是十进制数字77的原码。
接下来,从正数和负数的角度介绍一下原码、反码和补码在数字上的体现吧。
正数和0的三码
正数和0的原码、反码和补码都是同一个。 既🌰中的77的原码、反码和补码都是01001101
负数的三码
- 负数的反码:将其原码除符号位外,全部取反
- 负数的补码:在反码的基础上+1
| 符号位 | 数字位 | |
|---|---|---|
| -77的原码 | 1 | 1001101 |
| -77的反码 | 1 | 0110010 |
| -77的补码 | 1 | 0110011 |
了解了原码反码补码是什么之后,我们来探讨一下他们为什么会出现吧。
原码出现的原因
- 我们比较习惯的十进制不太方便直接运用在计算机中,因为这需要十种状态来代表0—9。在计算机中常见有两种状态,既高电压和低电压;所以两种状态0-1代表的计数方式便是二进制。
- 同时为了能让计算机区分正负,用最高位1代表负数,最高位0代表正数。
反码和补码出现的原因
- 反码和补码的出现是为了计算机能够计算负数,又或者说是为了计算减法。
- 计算机中运算靠的是加法运算器,不论是加减乘除底层统统转化为加法操作。
十进制推算
以十进制算法 77 - 10 = 67 为例,探讨一下计算机是怎么做负数操作的吧。
- 以100为桥梁。先加上100,之后再减去100,变为:
- 77 + (100 - 10) - 100
- 77+ 90 - 100
- 167 - 100
- 假设我们只能计算100以内的自然数,既取值范围是开区间(0,99);只有2位数字位。添加符号位,变为:
- 0_77 + (100 - 10)- 100
- 0_77 + 0_90 - 100
- 1_67 - 100
- 可以看到如果将结果 1_67 的符号位变为 0,再将 -100 去掉,就变成我们想要的结果了。所以有大佬提出在 -10 转为 0_90 - 100 时,直接去掉-100,并转换其符号位,变为:
- 0_77 + (100 - 10)- 100
- 0_77 + 1_90
- 2_67 因为最高位代表符号位只能取0或1,即用二进制表示,所以2变为10,为10_67,舍去溢出的数字,变为0_67
我们将这种思路带入二进制中
二进制推算
还以十进制 77 - 10 为例,转为二进制:100 1101 - 1010 = 100 0011
- 以 1000 0000 为桥梁
- 100 1101 + (1000 0000 - 1010) - 1000 0000
- 100 1101 + 111 0110 - 1000 0000
- 1100 0011 - 1000 0000
- 假设我们只能计算二进制 1000 0000 以内的数,添加符号位
- 0_100 1101 + (1000 0000 - 0_1010) - 1000 0000
- 0_100 1101 + 0_111 0110 - 1000 0000
- 1_100 0011 - 1000 0000
- 在 -1010 转为 0_111 0110 - 1000 0000时,直接去掉-1000 0000,并转位其符号位,变为:
- 0_100 1101 + (1000 0000 - 0_1010) - 1000 0000
- 0_100 1101 + 1_111 0110
- 10_100 0011 因为10_100 0011的下划线前代表符号位,舍去溢出的数字,变为0_100 0011
通过上述推算我们可以得出,要做二进制减法,可以将减数取1000 0000补数,并置换符号位
000 1010 关于1000 0000的补数为111 0110,它的各位取反数为 111 0101,他们之间相差1。下面我们再多观察几位数字的补数和各位取反数
| 二进制数 | 关于1000 0000的补数 | 各位取反数 |
|---|---|---|
| 101 0011 | 010 1101 | 010 1100 |
| 000 1110 | 111 0010 | 111 0001 |
| 001 0001 | 110 1111 | 110 1110 |
| 101 1001 | 010 0111 | 010 0110 |
| 111 1101 | 000 0011 | 000 0010 |
| 001 1101 | 110 0011 | 110 0010 |
| 001 1111 | 110 0001 | 110 0000 |
通过这几组数据,我们发现二进制数的各位取反数+1恰好都等于各自1000 0000的补数。这样负数的加法运算就变成了负数取反再+1即可。为什么要想法设法和各位取反数取得联系呢,因为对于计算机来说取反就是把原本的高电压转位低电压,低电压转位高电压,这个操作是相对简单的。
上述的推算同样适用于结果为负数的例子,不过在最后的-1000 0000时,要再次套入推算,这里就不多做推算了,感兴趣的小伙伴可以自己试试。在此声明此推算是我自己的设想,可能存在错误和漏洞,还望大佬指正。
二进制数的各位取反数便是反码,反码+1便是补码;这便是反码和补码的由来。
题外话题1:数据范围
计算机中存储的均为补码,以1字节存储为例
| 数字 | 原码 | 反码 | 补码 |
|---|---|---|---|
| 127 | 0111 1111 | 0111 1111 | 0111 1111 |
| 1 | 0000 0001 | 0000 0001 | 0000 0001 |
| 0 | 0000 0000 | 0000 0000 | 0000 0000 |
| -1 | 1000 0001 | 1111 1110 | 1111 1111 |
| -127 | 1111 1111 | 1000 0000 | 1000 0001 |
| -128 | - | - | 1000 0000 |
- 不看符号位,补码000 000 至 111 1111 代表 [2^0,2^8-1] 或 [-2^0,-2^8-1] 既[1,127]或[-1,-127]
- 符号位0,数字位全0代表0
- 符号位1,数字位全0代表-128
- 所以1字节的存储范围位[-2^8-1+1,2^8-1]既[-128,127]
题外话题2:范围溢出
1字节存储方式中 127+1=-128
class testByte {
public static void main(String[] args) {
byte b1 = 127;
byte b2 = 1;
byte result = (byte) (b1 + b2);
System.out.println(result); // -128
}
}
127的补码为0111 1111,+1得1000 00000,而-128的补码就是1000 0000
结束语
感谢阅读,觉得有用的小伙伴,希望能随手赏个点赞😬 文章有不对的地方还望大家能指点一二。