原码、反码、补码 出现的原因

479 阅读7分钟

原码、反码和补码是便于计算机进行运算所出现的。在我们探讨为什么会出现他们之前,首先介绍一下数字在计算机中的存储形式吧。

计算机存储数字

计算机存储的数字均为二进制;且最高一位是符号位,1代表负数,0代表正数。

举个🌰:十进制数字 77= 64+8+4+1 = 2^6+2^3+2^2+2^0 = 1001101。假设该数字用1字节存储,那么除了最高位的1比特位是区分符号的,还有7比特位存储数字。既77的“在计算机中的存储数字”为下表

符号位数字位
01001101

最终我们把77变成了01001101,这个数字就是十进制数字77的原码。

接下来,从正数和负数的角度介绍一下原码、反码和补码在数字上的体现吧。

正数和0的三码

正数和0的原码、反码和补码都是同一个。 既🌰中的77的原码、反码和补码都是01001101

负数的三码

  • 负数的反码:将其原码除符号位外,全部取反
  • 负数的补码:在反码的基础上+1
符号位数字位
-77的原码11001101
-77的反码10110010
-77的补码10110011

了解了原码反码补码是什么之后,我们来探讨一下他们为什么会出现吧。

原码出现的原因

  1. 我们比较习惯的十进制不太方便直接运用在计算机中,因为这需要十种状态来代表0—9。在计算机中常见有两种状态,既高电压和低电压;所以两种状态0-1代表的计数方式便是二进制。
  2. 同时为了能让计算机区分正负,用最高位1代表负数,最高位0代表正数。

反码和补码出现的原因

  1. 反码和补码的出现是为了计算机能够计算负数,又或者说是为了计算减法。
  2. 计算机中运算靠的是加法运算器,不论是加减乘除底层统统转化为加法操作。

十进制推算

以十进制算法 77 - 10 = 67 为例,探讨一下计算机是怎么做负数操作的吧。

  1. 以100为桥梁。先加上100,之后再减去100,变为:
    • 77 + (100 - 10) - 100
    • 77+ 90 - 100
    • 167 - 100
  2. 假设我们只能计算100以内的自然数,既取值范围是开区间(0,99);只有2位数字位。添加符号位,变为:
    • 0_77 + (100 - 10)- 100
    • 0_77 + 0_90 - 100
    • 1_67 - 100
  3. 可以看到如果将结果 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

  1. 以 1000 0000 为桥梁
    • 100 1101 + (1000 0000 - 1010) - 1000 0000
    • 100 1101 + 111 0110 - 1000 0000
    • 1100 0011 - 1000 0000
  2. 假设我们只能计算二进制 1000 0000 以内的数,添加符号位
    • 0_100 1101 + (1000 0000 - 0_1010) - 1000 0000
    • 0_100 1101 + 0_111 0110 - 1000 0000
    • 1_100 0011 - 1000 0000
  3. 在 -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 0011010 1101010 1100
000 1110111 0010111 0001
001 0001110 1111110 1110
101 1001010 0111010 0110
111 1101000 0011000 0010
001 1101110 0011110 0010
001 1111110 0001110 0000

通过这几组数据,我们发现二进制数的各位取反数+1恰好都等于各自1000 0000的补数。这样负数的加法运算就变成了负数取反再+1即可。为什么要想法设法和各位取反数取得联系呢,因为对于计算机来说取反就是把原本的高电压转位低电压,低电压转位高电压,这个操作是相对简单的。

上述的推算同样适用于结果为负数的例子,不过在最后的-1000 0000时,要再次套入推算,这里就不多做推算了,感兴趣的小伙伴可以自己试试。在此声明此推算是我自己的设想,可能存在错误和漏洞,还望大佬指正。

二进制数的各位取反数便是反码,反码+1便是补码;这便是反码和补码的由来。

题外话题1:数据范围

计算机中存储的均为补码,以1字节存储为例

数字原码反码补码
1270111 11110111 11110111 1111
10000 00010000 00010000 0001
00000 00000000 00000000 0000
-11000 00011111 11101111 1111
-1271111 11111000 00001000 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

结束语

感谢阅读,觉得有用的小伙伴,希望能随手赏个点赞😬 文章有不对的地方还望大家能指点一二。