导读:补码是如何产生的、计算机如何表示负数。
1.前提认知
(1)计算机中只有加法器,加减法使用的都是加法器,同时计算机通过加法器左移累加实现乘法运算、右移累减实现除法运算。
(2)补码是一种编码格式,它不是真实的数字。
我在之前的文章《从晶体管开始聊聊计算机为什么采用二进制》中说过计算机中所有机器码"0101"实际上是高低电平的不同排列组合。
至于这些"0101"数字真正表示的是什么人类可读的信息,是由计算机科学家制定的标准决定的。
这个标准就是我们常说的"编码格式"或"编码规则",比如"0000_0001"在某种标准下表示现实世界十进制1,但是在另一种标准下可以表示汉字"我",在另一种标准下表示"A"。
所以,学习原码、补码时不要认为它们是数字,它们并没有表示实际大小的意义。
(3)如下约定:"49-9=40"这个等式中49是被减数,9是减数。
2.补码的实质
2.1.补码的引入
假如我们面对的是一台十进制的2bit计算机,即这个计算机最多能存储两位数。
(1)那么如何让9变为0?
第一种方式是9-9,得数为0;
第二种方式是9+91,得数也为0。因为9+91=100,而2bit的计算机无法记录百位数1,所以得到0。
从这里可以发现一个规律,对于2bit的计算机来说,-9的计算效果相当于+91。
下面是对"49-9"按照这种计算效果进行计算:
49-9
=49+91
=140
因为3bit的140超出存储空间,所以要舍去百位的1,得数为40,即49-9=40。
(2)同样一台十进制的2bit计算机,我们可以计算一下"30-20"。
对于"20"这个数字来说,它要想变为0,要么减去20,要么加上80,所以"-20"的计算效果与"+80"相同。
接下来计算一下30-20:
30-20
=30+80
=110
舍去最高位1,得数10,即30-20等于10。
从上面的这两个例子就可以看出对于一台十进制的2bit计算机来说,减法可以通过加法来实现。
这里产生两个问题:(1)"减数"和替换它的"加数"之间有什么样的对应关系,即如何根据减数确定加数;
(2)为什么减法可以使用加法替代。
2.2.加数是减数的补码
对于"49-9=49+91"而言,减数9加上91正好为100,同样"30-20=30+80"中减数20加上80也是100。
这不是巧合,而是一种计算方式。
对于一台十进制的2bit计算机,这里面和减数一起加起来得到100的那个数就是减数的"补码",减法计算其实就是加上那个减数的补码。
2.3.为什么减法可以使用加法替代
在回答这个问题之前,先搞清楚对于一台十进制的2bit计算机,为什么要以100为补码计算的标准呢?
原因很简单,因为2bit计算机只能存储2位数,100这个3bit数字实际上就是00,数字当加到100时,自动减少100。
换句话说,对于2bit的十进制计算机来说,遇见100就清零,我这里简记为"遇百变零",这就是一个减法,只不过是一次就固定的减100。
计算机可以借助这个特性实现减法。
比如"30-20"是在30的基础上减掉20,我们"遇百变零"是减100,而此处只要减去20,多减去100,那么我们就加上80。
所以"30-20=30+80"。
这就是为什么减法可以使用加法替代。
进而也可以认识到,减数与其补码两个数相加之和会将所有位数清零。
2.4.减数补码的计算
上面已经说过:减数与其补码两个数相加之和会将计算机所有位数清零。
这就是减数的补码的计算方式。
对于十进制计算机来说,2bit计算机,减数 1的补码就是99;4bit计算机,减数1的补码是9999。
对于二进制计算机来说,2bit计算机,减数01的补码就是11;4bit计算机,减数0001的补码就是1111.
到了这里,我们用二进制验证一下,4bit计算机计算4-3。
3的二进制是0011,3作为减数,所以要变为补码与4相加,那么我们先计算一下0011的补码,如下表。
减数的二进制数与其补码之和会将4bit清零,可以看出0011的补码是1101,也就是减数3参与加法器计算时使用的是"1101"编码。
所以"4-3"在计算机内就是"0100+1101",结果为10001,最高位无法存储、舍去,结果为0001,如下表。
即4-3的计算结果为0001,转换为十进制,就是1。
到这里,我们暂停,先回忆一下:计算机内的减法计算实质上是被减数加上减数的补码。比如49-9=49+91=40.
对于N位(bit)计算机而言,一个减数与其补码之和可以使N位清零。
减数就是我们现实生活中人类可读的数字,补码是计算机识别的编码符号。
由减数转化为补码这个过程称为编码,如实际数字-9转换为补码91就是编码;
由补码转换为减数这个过程称为解码,如补码91转换为实际数字-9就是解码。
3.补码如何表示负数
本篇文章的两个知识点,补码的计算已经说完了。
下面说下一个知识点,补码表示负数的规律。
3.1.补码一分为二
因为计算机内没有减号、负号,科学家为了表示负数,就将数字区间一分为二,一半表示正数,另一半表示负数。
同样以十进制的2bit计算机作为说明,2bit计算机能够存储00到99一共100个数字。
不对!应该是00到99一共100个补码。
计算机识别的不是人类可读信息,而是这些可读信息转换的编码,一定要有这种认知!
科学家将00到49这50个补码作为正数,而且和实际数字一一对应,比如00表示实际的0,49对应实际的49。
之后将50到99这50个补码作为负数,但不是50表示实际数字-1,而是99表示实际数字的-1,50表示实际数字-50。
负数和补码之间之所以采用这种映射方式,还是因为"补码"这种编码格式:负数的绝对值(也就是减法中的减数)与其补码之和会使计算机所有位数清零。
比如-1的补码就是99,-50的补码是50。
反过来想(也就是解码),补码99表示实际数字-1,补码50表示实际数字-50.
总之,不论编码还是解码,使用的都是补码的规则:负数绝对值与补码之和清零所有位。
到这里你可能想明白了为什么8bit二进制计算机内补码1111_1111表示-1,为什么补码1000_0000是最大负数-128而不是-0了吧。
这样一来,00到99这100个补码就表示了实际数字-50到49。
二进制计算机也是如此,比如8bit计算机共有0000_0000到1111_1111一共256个补码。
256个补码一分为二,0000_0000到0111_1111共计128个补码表示正数,而且和实际数字一一对应。
比如补码0000_0000表示实际数字0,0000_1000表示实际数字8,0111_1111表示实际数字127.
1000_0000到1111_1111共计128个补码表示负数,和实际负数之间采用补码这种映射关系。
比如补码1111_1111表示-1,因为1111_1111加上0000_0001清零;补码1000_0000表示-128,因为1000_0000加上1000_0000清零。
如此一来,8bit二进制计算机就可以表示-128到127这些实际大小的数字了。
在计算机中,8位带符号二进制数的取值范围是[-128, 127],比如Java中byte类型。
这与我们上面的解释对应上了。
同时也澄清了一个误会,计算机最高位1表示负数,是因为1000_0000将256个补码恰好一分为二,用1000_0000及以后的补码表示负数。
计算机对于最高位是1的数字并不是将首位1直接变为负号,而是通过补码这种编码将其整体解码为负数。
所以不要将最高位的1与其它位的二进制数分离开来,分别看待。
3.2.补码的解码
在之前的案例中,我们实现减法都是大数减去小数,如49-9,3-20,4-3.但是如果换成小数减去大数,好像就出问题了。
如果你没有发现可以计算一下"9-49"。
9-49
=9+51
=60
竟然出现了"9-49=9+51=60"的情况?
9-49应该等于-40,怎么是60呢?
原因很简单,60是-40的补码。
计算机只识别补码60,之后就会解码将其转化为实际数字-40。
这时你可能想到了文章开篇说的一种认知——补码只是一种编码格式,它不是表示真正大小的数字。
此时60这个编码对应的实际数字就是-40,即9-49=-40。
到了这里,我们就了解了人类是如何给计算机赋予负号的含义。就是通过补码的编码与解码。
4.总结
4.1.运算数据都是补码形式
人类输入的运算信息在计算机中是以补码的形式存在的。不论是加法中的加数,还是减法中的被减数、减数,还是运算结果。
你可能想到了"49-9=49+91=40"中的"40"其实也是补码,只不过形式上和实际数字相同。
4.2.减法的执行过程
(1)减法中的被减数和减数首先按照补码这种编码格式转换为补码,被减数和补码从形式上看一模一样,减数则是与其补码之和清零。
(2)加法器执行加法。
(3)如果数据溢出,那么就将剩下的数据解码为实际数字;如果没有溢出,就将这个结果解码为实际数字。
以十进制2bit计算机为例,49-9转换为补码49和91,之后加法器相加,溢出一位得到补码40,解码为实际数字40;
9-49转换为补码是9和51,之后加法器相加,没有溢出得到补码60,解码为实际数字-40。
以上就是我对补码的初步认识。
最后祝大家2019年新起点、新征程,创造新辉煌!