1. 前言
我们知道,现代计算机主要采用数字集成电路搭建,数字电路通过高低电平只能表示0和1,所以计算机只认识0和1。无论是存储还是计算,计算机均采用二进制系统完成。例如:十进制的5用二进制表示为101
。
机器数
数字在计算机中的二进制表示形式叫作「机器数」。机器数有两个特点:
1、机器数是带符号的,即分正负。最高位0代表正数,1代表负数。
2、二进制位数受设备限制,字长8位叫一字节,机器字长一般是字节的整数倍,例如:32位、64位。
例如机器字长为8的情况下,十进制的5机器数表示为00000101
,-5机器数表示为10000101
。
真值
因为第一位是符号位,所以机器数的形式值不等于其真值。例如上述10000101
,真值为-5,二进制的形式值却是133。因此,将带符号位的机器数对应的真正数值称作机器数的真值。
2. 原码反码补码的概念
计算机直接使用二进制就好了,为啥要区分原码、反码和补码?在探究这个问题前,不着急,先了解一下它们的概念和计算规则。
1、原码:符号位+真值的绝对值。
5 = 00000101(原)
-5 = 10000101(原)
2、反码:正数的反码是它本身;负数的反码除符号位外,其余位取反。
5 = 00000101(反)
-5 = 11111010(反)
3、补码:正数的补码是它本身;负数的补码是它的反码+1。
5 = 00000101(补)
-5 = 11111011(补)
3. 演进过程
已经有原码了,为什么还要用反码和补码呢?是不是多此一举?非也!
对于计算机而言,实现加法器相对简单,相反减法器就略显复杂,需要考虑借位等逻辑。早期的计算机是有减法器的,后面因为效率太低慢慢被废弃了,能否设计一种编码,让符号位也直接参与运算呢?这样只需要加法器就可以同时完成加减法计算了,电路的设计会更加简单和高效。
3.1 原码的缺陷
使用原码直接进行运算,对于加法是没有问题的,例如5+5=10。
00000101(原)
+00000101(原)
=00001010
=10D
原码最大的问题是,无法将减法转换为加法。例如3-2,我们可以通过计算3+(-2)得到结果,原码的计算结果是-5,明显是错误的。
00000011(原)
+10000010(原)
=10000101(原)
=-5D
3.2 反码的瑕疵
反码的出现正是弥补了原码无法执行减法运算的问题,反码运算减法的规则:A-B=A+(-B),如果最高位发生了进位,则需要低位再加1。
还是拿上面的3-2的例子,反码的计算过程如下,最终结果为1。
00000011(反)
+11111101(反)
=100000000+1(最高位溢出)
=1D
反码的计算结果是对的,但是算法规则稍微有点复杂,需要考虑最高位溢位的情况,效率偏低,另外还有一点瑕疵:0有两种编码。
以1-1为例,反码的计算过程如下,结果为-0,其实也就是0。我们发现,在反码中,00000000
和11111111
都表示0,0有两种编码,在判断是否为0时需要判断两种编码,算是一点小瑕疵。
00000001(反)
+11111110(反)
=11111111(反)
=-0D
3.3 补码的完美
补码是现代计算机使用的编码格式,同时解决了原码的缺陷和反码的瑕疵。
首先,补码是可以将减法转换为加法的,以3-2为例,计算过程如下,结果正确。
00000011(补)
+11111110(补)
=100000001(补)最高位溢出,直接丢弃
=00000001(补)
=1D
其次,0不再有两种编码了,还是以1-1为例,计算过程如下,0只有一个编码,那就是00000000
。既然00000000
代表1,那11111111
代表什么呢?此时人为的规定它就是-128D,所以使用补码,1个字节的表示范围是[-128,127],比反码还多了一个数值。
00000001(补)
+11111111(补)
=100000000(补)最高位溢出,直接丢弃
=00000000
综上,我们发现,补码不仅可以将减法转换为加法,而且算法规则也更加的简单,不需要考虑最高位溢位的情况,发生溢位直接丢弃即可,计算效率也更高,而且补码还解决了反码中0有两种编码的问题。
反码和补码的计算过程并不复杂,但是要理解为什么还可以这么计算,就需要去了解这里面涉及到的数学知识了。
4. 补码的奥秘
模
首先,大家了解一下「模」的概念。任何计数系统都必须有两个参数:容量和精度,模就是衡量计数系统中容量的参数。我们以时钟举例,大家把12看作0,假设时间从0点开始。当时针转到11的时候,再往后转就没有12了,又重新回到了0,开始新的轮回,那么12就是时钟的模。
计算机系统同理,字长为8字节的机器,它最大能表示的值就是11111111
,则它的模是256。
模N加减法
在有「模」的前提条件下,当我们要计算减法时,我们其实有两种计算方法。以时钟为例,模M=12时,计算8-6的方式一是将时针正常往回拨6小时,得到2。方式二是将时针往前拨6小时,得到的结果也是2。
于是,我们可以得出这么一条结论,A-B = A+(M-B),+M相当于时针拨了一圈,并不影响结果,但是却可以将减法转换为加法。有的同学可能会有疑问,M-B不还是减法嘛,是的,但是这种减法不需要减法器即可实现。M-B称为-B的补数,计算一个数的补数,并不需要减法。
我们以4比特字长为例,最大值为1111
,即模M=16。计算5-3,其实和5+16-3=5+13的结果是一样的。我们发现3和13互为补数,其它补数还有例如2和14、1和15等,我们发现A-B等于A+B的补数,B的补数如何不通过减法快速求得呢?
3的补数由16-3计算而得,我们拆解一下这个公式,16-3=15-3+1,15二进制的表示四个位全是1,1111
-0011
其实就是0011
自身取反得1100
,最后再+1得1101
=13D就是它的补数。
模N加减法实现了「化减为加」,但是还有个问题,没有区分正负数。人们在计算时是需要考虑正负数的,所以就需要人为的将一部分二进制划分为负数,一部分二进制划分为正数。当模M=16时,根据公式A-B=A+16-B,当A为零点时,-B=16-B,即时针逆时针移动B,也可以由顺时针移动16-B得到。零点A可以是0000
到1111
中的任意一个数,为了方便计算,人为规定0000
为零点,那么0001
为+1,1111
为-1,1110
为-2,以此类推,结果都是符合补码定义的,大家可以算一下。
5. 总结
现代计算机采用数字集成电路搭建,通过高低电平来表示0和1,所以计算机均采用二进制系统完成。二进制只有0和1,无法表示符号,即正负数,因此人们定义了原码,最高位0代表正数,1代表负数。同时,计算机为了简单和高效,只有加法器,没有减法器,原码最大的问题是不能把减法转化为加法,所以有了反码。反码可以「化减为加」,但它的效率不高,需要考虑最高位溢位的情况,算法相对复杂,且0存在两种编码,有一定的瑕疵,最终,才有了现在的补码。补码同时解决了原码和反码的问题,采用模N加减法,不需要考虑最高位溢位的情况,遇到溢位直接丢弃,效率非常高。