补码及应用

1. 引言

静态随机存储器SRAM利用门电路触发器的两种稳定的电平(压)状态表示0和1,动态随机存储器DRAM利用电容器的放电与充电状态表示0和1,CPU内部的寄存器通常采用SRAM来实现。总之,计算机利用电子器件、电子电路、电磁现象等的两种不同的物理状态表示0和1,因此计算机内部的世界是0和1的世界,一切信息都用0和1来表示。

由0和1组成的序列叫作二进制模式(pattern)。根据乘法原理,全部4位二进制模式有2×2×2×2=24=16个,它们是

0000,0001,0010,……,1110,1111;

全部8位二进制模式有28(256)个,它们是

00000000, 00000001, 00000010, ……, 11111110, 11111111;

全部n位二进制模式有2n个。

为了表示和处理信息,就必须按照一定的对应法则对二进制模式进行指派和解释,使之具有唯一确定的意义,这个过程叫作编码。

标准ASCII码规定了27(128)个7位二进制模式与128个拉丁字母、数字及其它符号之间的对应关系,其中10个阿拉伯数字0~9的编码如下表所示:

十进制数字字符0~9的ASCII码

大写拉丁字母A~Z的ASCII码分别为1000001~1011010,小写拉丁字母a~z的ASCII码分别为1100001~1111010,编码连续不间断。

中国国标码GB18030规定了二进制模式与汉字及其它符号之间的对应关系,采用单字节、双字节、四字节三种长度的编码,共收录70244个汉字及其它符号,其中“中国”二字的编码分别是

中(D6D0)(11010110,11010000),国(B9FA)(10111001,11111010)

对于28(256)个8位二进制模式00000000~11111111,根据二进制位置记数法,可解释为8位无符号二进制整数,相应等值的无符号十进制整数为0~255,因此把8位二进制模式00000000~11111111依次规定为无符号十进制整数0~255的原码,这种等值对应关系既是人为规定的,也是一种自然解释,便于二-十进制数相互转换。通常,二进制模式与无符号二进制整数是同义词,不必严格区分。

计算机中的二进制模式可根据实际需要、按照不同编码方案进行解释并相应处理,可以根据ASCII码表或EBCDIC码表解释为字母或数字,可以根据中国国标码GB18030解释为汉字或中文符号,可以解释为无符号数或带符号数,可以解释为整数或小数,可以解释为由CPU识别执行的机器指令等等。

2. 补数概念

通常说的8位、16位、32位、64位CPU是指其运算器的操作数与结果分别是8位、16位、32位、64位的。为简单起见,这里采用一个四位CPU模型来说明问题,如下图所示:

四位CPU模型:操作数与结果均为4位二进制模式,可以解释为无符号二进制整数相加,1011+0011=1110(11+3=14)

不管CPU位数多少,其共性是:运算器只做加法运算,减法通过加法实现,这样就可以省去减法电路、简化设计。那么,如何用加法实现减法呢?答案就是利用补码。顺便指出,乘法与除法通过加法与减法实现,因此机器计算最终归于加法。

二进制加法规则有四条:0+0=0,0+1=1,1+0=1,1+1=(1)0,括号内的1表示进位。例如:

运算器像小学生那样做无符号二进制整数的加法

无论运算器位数多少,加法操作时,最高位总可能产生进位,那么,如何处理这个进位呢?答案是丢弃!也就是说,在结果中不保留这个进位。若最高位产生进位,则把标志寄存器中的进位标志C设置为1供程序检测。这种处理方式既简单,又为补码化减为加创造了条件。

不妨写出全部四位无符号二进制整数,如下表所示:

四位二进制模式解释为等值无符号十进制整数的原码

对于四位无符号二进制整数,从0000开始,依次加1,在第15次加1后,得到1111,当第16次加1时,即1111+1时,最高位就会产生进位,丢弃后得到0000,这就形成了一个闭环,如下图所示。假如在纸上手工演算,可以保留这个进位,得到10000(与十进制数16等值),这才是正确结果,但运算器的位数毕竟是有限的,丢弃是合理的技术选择。正是根据这一特点,计算机前辈定义了补码。

在四位运算器中,1111+1=0000形成闭环,这是定义补码的前提

对于四位无符号二进制整数,若两数之和为10000,则这两个数互为补数。在上图中,背景色相同的两个数互为补数,它们是(0001,1111),(0010,1110),(0011,1101),(0100,1100),(0101,1011),(0110,1010),(0111,1001),(1000,1000),(0000,0000)。注意:根据两数之和为10000的定义,0000与10000互为补数。由于采用四位运算器执行加法,对于任意四位无符号二进制整数a,10000+a再丢弃最左边的1得到a,而0000+a也得到a,例如:10000+1011=(1)1011;0000+1011=1011。在这个意义下,10000与0000等效,所以规定0000的补数为0000,允许0000以补数身份代替10000参与加法运算。这样,任意四位无符号二进制整数都具有唯一四位补数,且补数关系是相互的。

给定任意四位无符号二进制整数,除按定义求补数以外,还可以将其各位变反(0变1,1变0),再在末位加1,来求得补数,例如:将0101各位取反得1010再在末位加1得到补数1011,这个过程叫作变补,运算器就是这样求的。

由于二进制数的基数为2,上述补数概念称为2的补数,简称补数

3. 补数概念拓展

仿照2的补数定义,可定义1的补数。对于四位无符号二进制整数,若两数之和为1111,则这两个数互为1的补数。全部1的补数对包括(0000,1111),(0001,1110),(0010,1101),(0011,1100),(0100,1011),(0101,1010),(0110,1001),(0111,1000)。之所以叫作1的补数,是因为两数之和为1111,四位都是1。

给定任意四位无符号二进制整数,除按定义求1的补数以外,还可以将其各位变反(0变1,1变0),来求得1的补数,例如:将0101各位取反得到1010,这个过程叫作变反,运算器就是这样求的。1的补数因此叫作反数。1111-0101=1010,从形式上看就是变反,而10000-0101=(1111-0101)+1=1010+1,从形式上看就是变反再加1。

可类比定义10的补数和9的补数。对于三位无符号十进制整数,若两数之和为1000,则这两个数互为10的补数。例如:(499,501)就是其中一对。因为十进制数的基数为10,所以称为10的补数

对于三位无符号十进制整数,若两数之和为999,则这两个数互为9的补数。例如:(499,500)就是其中一对。因为两数之和为999,三位都是9,所以称为9的补数

综上所述,补数存在的客观条件是限定位数的有模运算。若限定无符号整数的位数为n,则模量就是2n( 二进制) 或10n(十进制)。计算机的运算是最典型的有模运算,四位运算器的模是24=16(10000),恰好是全部四位二进制模式的个数,即上面彩色闭环中的扇区数。有模运算的概念可以进一步推广,模的大小可根据实际需要任意规定。

请读者自行定义8位、16位、32位补数,并列举全部8位补数对。

2的补数从two‘s complement翻译而来。

1的补数从one‘s complement翻译而来。

4. 补码定义

以四位运算器进行说明。全部四位无符号二进制整数组成一个集合{0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111},与之对应的等值十进制整数集合为{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}。基于位置记数法和这种天然对应关系,上文中定义了原码,即一个四位二进制模式被解释为一个无符号整数,以十进制形式表示出来。

为表示带符号整数,就要对全部16个二进制模式进行编码指派,使每个模式代表一个带符号整数。编码方案必须实现化减为加,又便于二-十进制相互转换,还要有助于溢出判别。前人发明了两种编码方案:反码方案和补码方案,如下表所示。在反码方案中,用无符号整数0000至0111分别表示+0至+7,用无符号整数0000至0111的反数分别表示-0至-7。在补码方案中,用无符号整数0000至0111分别表示0至+7,用无符号整数0001至1000的补数分别表示-1至-8。从表面来看,是把全部16个二进制模式等分为两组,用0开头的8个模式表示非负数,用1开头的8个模式表示负数。从本质讲,是利用反数对或补数对来表示相反数对,从而实现化减为加。

四位二进制模式解释为带符号十进制整数的反码和补码

在上表中,把二进制模式分别规定为对应带符号十进制整数的反码和补码。由于补码方案应用最广,下文着重讨论补码方案。

请读者自行描述8位、16位、32位补码方案,并列举全部8位补码。

5. 化减为加

如下图所示,彩环由16个四位二进制模式组成,彩环中的模式既解释为内环中对应的无符号十进制整数的原码,又解释为外环中对应的带符号十进制整数的补码。任意四位二进制模式既可解释为无符号十进制整数,也可解释为带符号十进制整数。究竟采用哪一种解释?需要根据实际问题决定并选用相应指令进行处理。

四位二进制模式基于原码和补码对应规则分别解释为无符号和带符号十进制整数

设某人站在彩环的任意扇区m,沿顺时针方向走n步(一步一扇区),到达扇区p,这意味着m+n=p。按照原码编码规则,把m、n和p转换为等值的无符号十进制整数,机器就实现了无符号十进制整数的加法运算。按照补码编码规则,把m、n和p转换为对应的带符号十进制整数,机器就实现了带符号十进制整数的加法运算。

加法实例如下图所示。运算器像小学生那样,按照二进制加法规则,对二进制模式执行加法运算,二进制模式的意义则根据实际需要进行解释,CPU的加法指令就是这样工作的。

加法指令的工作

设甲乙二人站在彩环的任意扇区,甲逆时针走n步(一步一扇区),乙顺时针走m步,若m与n互为补数,则甲乙必定到达同一扇区。逆时针走n步相当于减n,顺时针走m步相当于加m。这段话等价于说

给定任意无符号二进制整数a和b,必有

a-b = a+[b]补数

其中 0000≤a, b≤1111。这就是说,减去一个数,可以通过加上它的补数来实现。运算器对b执行变补操作,得到b的补数后再与a相加,CPU的减法指令就是这样工作的。

按照原码编码规则,把a和b转换为等值的无符号十进制整数,机器就实现了无符号十进制整数的减法运算。

可以证明,给定任意带符号十进制整数a和b,必有

[a-b]补码 = [a]补码+[-b]补码

其中 -8≤a, b≤+7。这就是说,减去一个数,可以通过加上它的相反数的补码来实现。运算器对b的补码执行变补操作,得到-b的补码后再与a相加,CPU的减法指令就是这样工作的。

在上述两种情况下,CPU减法指令的工作是完全相同的,都是先对减数b的二进制模式执行变补操作后,再与a相加,因此无符号整数与以补码表示的带符号整数的减法共享同一减法指令。

顺便指出,给定任意带符号十进制整数a和b,必有

[a+b]补码 = [a]补码+[b]补码

其中 -8≤a, b≤+7。这与无符号二进制整数的加法是一致的,因此无符号整数与以补码表示的带符号整数的加法共享同一加法指令。

减法实例如下图所示。原本是要计算1100-0011的结果,运算器却是先求得0011的补数1101,再通过执行1100+1101来实现。CPU的减法指令就是这样工作的。这相当于甲乙二人起初站在上图彩环的扇区1100,甲逆时针走0011(3)步,乙顺时针走1101(13)步,最终甲乙到达同一扇区1001,因为0011与1101互为补数。

减法指令的工作

在上面两个实例图中,二进制模式既可解释为无符号十进制整数,也可解释为带符号十进制整数,共享同一套加减指令。

为了理解更全面,下面以三位无符号十进制整数说明利用补数化减为加的原理,例如:若要计算846-571,则先求得571的补数429,再计算846+429=(1)275,丢弃最高位进位1,得到正确结果275。又一次说明,减去一个数,可以通过加上它的补数来实现。

利用反码也可实现化减为加,但结果需要加1进行修正。反码存在+0和-0,无法表示-128(8位)或-32768(16位),这些都是缺点。

补码编码方案欣赏:

  • 补码编码的对应法则是天才的决定。
  • 沿彩环顺时针方向,为全部16个二进制模式指派连续增1的16个带符号十进制整数,若把二进制模式解释为无符号二进制整数,也是连续增1的,因此二者是顺时针同步连续增1的数列,同时也决定了顺时针方向是加法的方向。
  • 因1111+1=0000,且-1+1=0,故可用1111表示-1。
  • 因1111与0001互为补数,且-1与+1互为相反数,故可用1111表示-1,用0001表示+1。对于无符号数而言,减去一个数等于加上它的补数。对于带符号数而言,减去一个数等于加上它的相反数。补码规则用补数对表示相反数对,从而实现吻合。
  • 用1000表示-8而非+8,且用0111表示+7使得二进制模式左边最高位成为符号位,1表示负数,0表示非负数,有助于正负判别和溢出判别,而且保证了负数与非负数个数相等。
  • 用0001至1000的补数分别表示-1至-8,有助于确定负数的绝对真值(将其变补即可),这就便于二-十进制相互转换,为输入输出提供了保障。
  • 补码编码方案是唯一正确、高效的化减为加方案。
  • 无符号二进制整数的补数与带符号十进制整数的补码是不同的概念。
  • 二进制模式可解释为无符号二进制整数,根据无符号二进制整数的有模运算特点定义其补数和反数,利用无符号二进制整数的补数和反数定义带符号十进制整数的补码和反码。
  • 补码化减为加只是补数化减为加的表现形式,后者才是根本。

6. 溢出判别

因为运算器位数固定,所以数的表示范围有限。四位无符号整数的表示范围是0~15,四位带符号整数的补码表示范围是-8~+7;八位无符号整数的表示范围是0~255,八位带符号整数的补码表示范围是-128~+127。十六位无符号整数的表示范围是0~65535,十六位带符号整数的补码表示范围是-32768~+32767。溢出是指运算结果超出表示范围,导致结果错误,必须判别发现。由于加减运算对于表示范围是不封闭的,所以溢出必然会发生。

CPU中有个寄存器,叫作标志寄存器,也叫状态寄存器,它的每一位都反映运算器当前运算结果的一种特征,例如:进位标志C表示结果最高位是否发生进位或借位;溢出标志V表示结果是否发生溢出;符号标志S表示结果的正负;零标志Z表示结果是否为0。在上文图中只画出了四个标志位,真正的CPU还有更多的标志位。

以四位运算器进行说明。对于无符号整数的加减运算,如果两数相加且结果大于15,这叫上溢,进位标志C为1,反之,进位标志C为0。如果两数相减且不够减,进位标志C为1,反之,进位标志C为0,这种情况下发生借位,进位和借位是同一标志位。

对于带符号整数的加减运算,如果同号相加或异号相减且结果大于+7,这叫上溢,若结果小于-8,这叫下溢。无论发生上溢还是下溢,溢出标志位V置1,反之,溢出标志V清0。其它情况不会发生溢出。

小结

  • 运算器只做加法,减法通过加法实现,以降低电路复杂度。
  • 无符号整数利用补数化减为加,减去一个数,等于加上它的补数;以补码表示的带符号整数利用补码化减为加,减去一个数,等于加上它的相反数的补码。该结论在数学上可严格证明。
  • 无符号整数与以补码表示的带符号整数共享同一套加减指令。
  • 无符号整数加法溢出或不够减用进位/借位标志位C来判别。
  • 以补码表示的带符号整数加减溢出用溢出标志位V来判别。
  • 二进制模式的意义根据实际需要进行解释并相应处理。