定点加法与减法运算
上节内容介绍过,带符号数有原码、反码、补码等几种表示方法,并且为了降低运算器的复杂性,我们可以把减法看作被减数加减数的负数,即
A-B=A+(-B)
这样减法操作就可以用加法操作来代替,运算器只需要设置加法器即可,无须再设置减法器。此外,由于原码加法运算复杂,还需要考虑双方操作数的符号位。而计算机中的有模运算A+(-B)中的-B可以用它的补码来代替,实现相对简单,运算过程中无须再额外考虑符号位。因而现代计算机中都采用补码做加减运算。
1.补码加减运算
运算器中补码加减的基本公式如下:
[A+B]补=[A]补+[B]补
[A-B]补=[A+(-B)]补=[A]补+[-B]补
公式表明,当作加减运算时,可直接将补码表示的两个操作数[A]补和[B]补相加。只要结果不超出机器字长所能表示的数值范围,符号位可与数值位等同处理。如果符号位在运算过程中产生向上进位,根据前面讲述补码时关于有模运算的概念可知,运算器会自动舍去,不会影响结果的正确性。当然,也可能由于加减运算的结果超出了机器字长所能表示的范围而产生错误(称为溢出)的情况。
【例】已知十进制数A=+18,B=+23.设机器字长为8位,用补码加减计算[A+B]补并还原成真值。
使用二进制表示:
A=+10010,B=+10111
求A和B的原码,得
[A]原=00010010,[B]原=00010111
求A和B的补码,得
[A]补=00010010,[B]补=00010111
根据补码加减公式,得
[A+B]补=[A]补+[B]补=00010010+00010111,结果为:
[A+B]补=00101001
[A+B]原=00101001
A+B=(41)10进制
【例】已知十进制数A=-10,B=-2.设机器字长为5位,用补码加减法计算[A+B]补并还原成真值。
使用二进制形式表示:
A=-1010,B=-0010
求A和B的原码,得
[A]原=11010,[B]原=10010
求A和B的补码,得
[A]补=10110,[B]补=11110
根据补码加减公式,得
[A+B]补=[A]补+[B]补=10110+11110=110100(机器字长为5,故最高位溢出丢弃)
故[A+B]补=10100 补码的补码即为原码
[A+B]原=11100
A+B=-12
【例】已知二进制纯小数A=+0.1001,B=+0.0101.设机器字长为5位,使用补码加减法计算[A+B]补并还原成真值。
求A和B的原码,得
[A]原=0.1001,[B]原=0.0101(实际编码不存在小数点)
求A和B的补码,得
[A]补=0.1001,[B]补=0.0101
根据补码加减公式,得
[A+B]补=[A]补+[B]补=0.1001+0.0101=0.1110
A+B=0.1110
当作减法操作A-B时,经过公式推导可知:只需先求出[-B]补,就可以按照加法规则[A-B]补=[A]补+[-B]补进行运算。
【例】已知二进制纯小数A=+0.1001,B=+0.0101.设机器字长为5位,使用补码加减法计算[A-B]补并还原成真值。
-B=-0.0101
[A]原=0.1001,[-B]原=1.0101
[A]补=0.1001,[-B]补=1.1011
根据补码加减公式,得
[A-B]补=[A]补+[-B]补=0.1001+1.1011=10.0111(最高位1溢出丢弃)
即 [A-B]补=0.0100 A-B=(0.01)2进制
【例】已知十进制数A=-71,B=+43.设机器数字长为8位。用补码加减法计算[A-B]补并还原成真值。
使用二进制形式表示:
A=-1000111,-B=-101011
求A和B的原码,得
[A]原=11000111,[-B]原=10101011
求A和B的补码,得
[A]补=10111001,[-B]补=11010101
根据补码加减公式,得
[A-B]补=[A]补+[-B]补=10111001+11010101=110001110(最高位1溢出丢弃)
即[A-B]补=10001110
[A-B]原=11110010
A-B=-114
2.溢出判断
前面的例题的正确实际上都还要有一个共同的前提:运算结果没有超出机器字长所能表示的数值范围。我们知道,计算机运算器中进行的都是有模运算,机器字长所能表示的数值范围有限。所以,必须考虑运算结果是否超出机器数所能表示的范围。
【例】已知十进制数A=+71,B=+63.设机器数字长为8位。用补码加减法计算[A+B]补并还原成真值。
使用二进制形式表示:
A=+1000111,B=+111111
求A和B的原码,得
[A]原=01000111,[B]原=00111111
求A和B的补码,得
[A]补=01000111,[B]补=00111111
根据补码加减公式,得
[A+B]补=[A]补+[B]补=01000111+00111111=10000110
为[A+B]补=10000110
[A+B]原=11111010
A+B=-122
得到还原后的真值发现很明显的错误:两个正数相加,结果却为负数。这是因为8位有符号定点整数的取值范围为-128~+127,而上例中的A+B的数学运算结果应为134,已经超出了取值范围。在计算机中,这种由于运算结果超出机器数所能表示的范围而导致的错误现象称为溢出。下面我们来分析一下,什么时候可能会产生溢出,运算器中如何判断一个运算结果是否溢出。
首先,简单分析不难发现,两个不同符号的数相加或两个相同符号的数相减,由于结果的绝对值一定会小于其中一个或两个操作数的绝对值,所以结果一定不会出现溢出。只有符号不同的两个数相减或符号相同的两个数相加,结果的绝对值一定会大于两个操作数的绝对值,才有可能出现溢出。
下面我们以机器字长为5位的有符号数的加减为例,通过多个实例的分析,寻找溢出发生的规律,从而找到判断溢出的方法。
【例】5+4=9(未溢出)
转换为补码进行计算,得
00101+00100=01001
【例】12+7=19(溢出) 下列均为补码运算
01100+00111=10011
【例】-10+(-2)=-12(未溢出)
10110+11110=110100——10100(丢弃最高位)
【例】-10+(-8)=-18(溢出)
10110+11000=101110——01110
【例】-5-12=-5+(-12)=-17(溢出)
11011+10100=101111——01111
【例】10-5=10+(-5)=5(未溢出)
01010+11011=100101——00101
【例】-10-(-5)=-10+5=-5(未溢出)
10110+00101=11011
从上面可以看出,机器字长为5位的有符号定点整数的取值范围为-16~15(即-2^n~2^n -1),数值运算的结果超出这个范围的即溢出。
(1)溢出判断方法一。不论是减法运算还是加法运算,在计算机中是通过补码变换,使用加法器进行两个补码的加法运算来实现的。只要同时满足下面两个条件,即为溢出:
第一,加法器中实际参加加法运算的两个补码符号位相同;
第二,加法器输出结果与这两个操作数的符号不同。
设加法器中实际参加加法运算的两个补码符号位分别为SA和SB,加法器输出结果为Sr,那么判断溢出的逻辑表达式为:
V=`SA`SBSr+SASB`Sr
【例】设二进制小数A=-0.1011,B=-0.0111.使用溢出判断方法一判断定点小数的运算:A+B的结果是否溢出。
求A和B的原码,得
[A]原=1.1011,[B]原=1.0111
计算操作数的补码,得
[A]补=1.0101,[B]补=1.1001
[A+B]补=[A]补+[B]补=1.0101+1.1001=10.1110——0.1110
参与加法运算的两个补码:1.0101和1.1001的符号位相同,均为1,而补码加法后的结果0.1110的符号位为0,与操作数的符号不同。据此判断,A+B的结果溢出。
【例】设二进制纯小数A+0.1001,B=+0.0101.使用溢出判断方法一判断定点小数的运算:A+B的结果是否溢出。
求A和B的原码,得
[A]原=0.1001,[B]原=0.0101
求A和B的补码,得
[A]补=0.1001,[B]补=0.0101
根据补码加减公式,得
[A+B]补=[A]补+[B]补=0.1001+0.0101=0.1110
参与加法运算的两个补码:0.1001和0.0101的符号位相同,均为0,而补码加法后的结果0.1110的符号位为0,与操作数的符号相同。据此判断,A+B的结果未溢出。
(2)溢出判断方法二。除了方法一,还可以从两个进位信号之间的关系中找到规律:分别为符号位产生的进位Cf和最高有效位(符号位右边第一位)产生的进位C。当符号位和最高有效数值位均产生进位,或均不产生进位时,补码加法运算没有出现溢出;而当符号位和最高有效位中只有一个产生进位,而另一个没有产生进位时,运算结果溢出。所以我们总结出第二个判断溢出的逻辑表达式为:
V=Cf异或C
(3)溢出判断方法三。第三种方法相对简单,但需要对补码的编码方式稍作改变,这种编码方式称为变形补码:相对普通的补码,变形补码还需要一个额外的一个符号位,两个符号位的取值相同,分别位于机器数编码的最高位和次高位,也称双符号位。例如,一个机器数字长为5位的补码为:01001,它的变形补码的编码为001001,当然,这是加法器中的寄存器字长也需要扩充一位,即6位。再如,当补码为10101101时,对应的变形补码的编码为:110101101.设真值A的变形补码用[A]补`来表示,使用变形补码进行定点数的加减运算时,其公式和使用普通补码进行加减运算的公式相似:
[A+B]补`=[A]补`+[B]补`
[A-B]补`=[A+(-B)]补`=[A]补`+[-B]补`
一般情况下,数据在存储器中仍保持单符号位,在将补码送入加法器进行运算之前,再扩充为两位符号位,运算结果也是变形补码形式。然后再将结果去掉一位符号位,变成普通补码形式存入存储器。使用变形补码的双符号位的作用就是判断运算结果是否溢出的。
下面我们以6位字长的变形补码的加减为例,分析并找出判断溢出的方法:
【例】5+4=9(未溢出)
000101+000100=001001
【例】12+7=19(溢出)
001100+000111=010011
【例】6-(-9)=6+9=15(未溢出)
000110+001001=001111
【例】5-(-11)=5+11=16(溢出)
000101+001011=010000
【例】-5-10=-5+(-10)=-15(未溢出)
111011+110110=1110001——110001
【例】-5-12=-5+(-12)=-17(溢出)
111011+110100=1101111——101111
【例】10-5=10+(-5)=5(未溢出)
001010+111011=1000101——000101
【例】-10-(-5)=-10+5=-5(未溢出)
110110+000101=111011
观察运算结果发现,变形补码加法的结果的两位符号位如果不相等,表示计算结果出现溢出。当结果的两位符号位相等,则表现未出现溢出。此时将结果去掉一位符号位,结果被还原成普通补码形式。所以,设变形补码加法运算后的结果第一位符号位和第二位符号位分别用Sf1和Sf2表示,则第三个判断溢出的逻辑表达式为:
V=Sf1异或Sf2
【例】设二进制纯小数A=0.1001,B=0.1101,使用溢出判断方法三判断定点小数的运算A+B的结果是否溢出
求A和B的原码,得
[A]原=0.1001,[B]原=0.1101
求A和B的变形补码,得
[A]补`=00.1001,[B]补`=00.1101
[A+B]补`=[A]补`+[B]补`=00.1001+00.1101=01.0110
变形补码运算结果为:01.0110,两个符号位不一致,可以判定,运算结果溢出。
定点乘法运算
计算机中,乘法运算是非常常用的一种运算,但是由于计算机中实现乘法运算比实现加减法运算要复杂很多,也有一些简单的CPU内不设置乘法器,乘法的实现是按照乘法器做乘法运算的流程,以加法器为基础,用软件编程的方式实现的。下面我们就来讨论乘法运算在计算机中的实现步骤。
定点乘法运算的实现方法很多,难易程度也不同。我们以原码1位乘运算为例,介绍计算机中实现定点乘法的基本思想和方法。
(1)符号位。首先是符号位的确定。与定点数的补码加减法运算不同,乘法运算的符号位无法通过转换补码,加入到乘法运算中,必须单独进行处理。根据乘法运算规则:同号相乘为正、异号相乘为负。设两个定点数分别为X和Y,Xf和Yf分别代表定点数X和Y的符号位,乘法运算结果为Z,Zf代表Z的符号位结果。如下所示(0正1负)
Xf | Yf | Zf |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
根据真值表,可得乘法运算符号位的逻辑表达式为:
Zf=Xf异或Yf
(2)数值部分乘法。然后,除去符号位,再单独考虑被乘数和乘数的绝对值的乘法运算。原码一位乘法的方法是从笔算乘法演变而来的,我们先看一个乘法运算笔算式子的完整过程。
【例】设二进制纯小数X=0.1101,Y=0.1011,计算X*Y
0 . 1 1 0 1
× 0 . 1 0 1 1
------------------------------------------------------
0 . 0 0 0 0 1 1 0 1……………………X*2^-4 X右移4位
0 . 0 0 0 1 1 0 1 ……………………X*2^-3 X右移3位
0 . 0 0 0 0 0 0 ……………………X*0
0 . 0 1 1 0 1 ……………………X*2^-1 X右移1位
------------------------------------------------------
0. 1 0 0 0 1 1 1 1
结果为:X*Y=0.10001111
根据上面的例子可得,二进制数的乘法X*Y的绝对值计算方法为:查看乘数Y的各位上的值,当为0时,记中间结果0;当为1时,记中间结果为X右移相应位数。然后将中间结果累加起来。
我们可以根据这种方式,以移位器和加法器为基础,或者编写程序代码,实现乘法运算。但是直接使用笔算乘法的方式,计算机需要更多额外的寄存器来存放这些中间结果。而且每一个中间结果的位数都比被乘数和乘数增加一倍,实现起来效率不高。所以,我们对上例的笔算乘法稍作一些改进,使其更方便地在计算机中实现(若X或Y为负数,则要取绝对值):
|X*Y|=X*0.1011
=0.1X+0.001X+0.0001X
=0.1X+0X+0.001(X+0.1X)
=0.1X+0.01(0X+0.1(X+0.1X))
=0.1(X+0.1(0X+0.1(X+0.1X)))
=2^-1(X+2^-1(0X+2^-1(X+2^-1X)))
=2^-1(X+2^-1(0X+2^-1(X+2^-1(X+0))))
被乘数乘以2^-1相当于乘数右移一位。观察上式,可以发现,计算X*Y的绝对值的操作,被表示成了一个先相加再右移的递归操作。这无论对于使用硬件方式进行乘法操作,还是对于软件编程方式进行乘法操作,都是非常容易实现的。下面我们分部计算上例的乘法:
①计算X+0,得中间结果0.1101;
②中间结果右移一位,得中间结果0.01101;
③上步的中间结果+1X,得中间结果1.00111;
④上步的中间结果右移一位,得中间结果0.100111;
⑤上步的中间结果+0X,得中间结果0.100111;
⑥上步的中间结果右移一位,得中间结果0.0100111;
⑦上步的中间结果+1X,得中间结果1.0001111;
⑧上步的中间结果右移一位,得最终结果0.10001111.
这样,乘法运算被分解为了简单的加法操作和移位操作。总结一下,设Y=0.Y1Y2……Yn,X^*为被乘数X的数值部分。对公式进行分步求解,可以得出二进制乘法操作的分步操作,步骤如下所示:
Z0=0
Z1=2^-1(Z0+X*Yn)
Z2=2^-1(Z1+X*Yn-1)
………………
Zi=2^1(Z(i-1)+X*Y(n-i+1))
………………
Zn=2^-1(Z(n-1)+X*Y1)
每个步骤中产生的这些中间结果:Z0,Z1,Z2,……Zn-1,我们称之为部分积。最后的Zn即为X*Y的结果Z的绝对值。
再加上上面讲到的符号位的处理,可得出原码一位乘法的完整算法如下:
①将被乘数和乘数的符号位进行异或操作:Zf=Xf异或Yf,得到结果的符号位。然后使用被乘数和乘数的数值部分进行计算,计算结果的绝对值。
②设部分积Z0位0.
③以乘数的最低位作为乘法的判别位,若判别位为1,则在部分积上加上被乘数,结果右移一位,若判别位为0,则在部分积上加0,结果右移一位。如此形成新的部分积。
④乘数右移一位。
⑤重复执行第三步和第四步,共执行n次,n为乘数数值部分长度。最后得到的部分积结果就是乘法结果的绝对值部分。
⑥将乘法操作的符号位和绝对值部分结合起来,得到乘法操作的最终结果。
【例】设二进制存小数X=-0.1001,Y=0.1010,试使用原码一位乘法计算X*Y,并写出详细的运算步骤。
求X和Y的原码
[X]原=1.1001,[Y]原=0.1010
首先计算符号位,Xf=1,Yf=0,得
Zf=Xf异或Yf=1异或0=1
被乘数X的数值部分为X*=0.1001,乘数为0.1010.按照原码一位乘法运算步骤如下:
①部分积Z0=0,乘数为0.1010;
②乘数最低位为0,Z0+0=0,结果右移一位,得Z1=0;
③乘数右移一位,得0.0101;
④乘数最低位为1,Z1+X*=0.1001,结果右移一位,得Z2=0.01001;
⑤乘数右移一位,得0.0010;
⑥乘数最低位为0,Z2+0=0.01001,结果右移一位,得Z3=0.001001;
⑦乘数右移一位,得0.0001;
⑧乘数最低位为1,Z3+X*=0.101101,结果右移一位,得Z4=0.0101101.即X*Y的绝对值为:0.0101101.
符号位与绝对值部分结合,得最终结果:
[Z]原=1.0101101
Z=-0.0101101