2.2.3 定点数的加减运算
00:00
好的,这一小节中我们要探讨的是定点数的加减运算。我们之前说过,在进行加减运算的时候,我们通常只会探讨原码和补码,而反码一般来说是不会直接参与这种加减乘除运算的,所以我们只探讨原码和补码的加减运算。另外当进行加或者减的时候,有可能会出现溢出的情况,那么计算机硬件是用什么逻辑来判断溢出的呢?这个是我们之后会探讨的问题。
00:31
首先来看一下原码的加减运算,其实这个部分在之前的小节中我们有简要的探讨过,比如说-14和14这2个数进行相加的操作。那如果加法器直接使用它们的原码进行相加,最终得到的这个结果是一个错误的结果。那我们的处理办法是把-14改为+14,然后加法变成减法。这样的运算用这样的方式来等价正14加上-14的一个效果,最终就可以得到正确的结果。
01:05
对于原码的加法来说,左边是被加数,右边是加数。被加数有可能出现两种情况,一种是正数,一种是负数,而加数也有可能出现正和负这样的两种情况,所以原码的加法操作有可能出现四种情况,就是被加数和加数分别为正或者为负的两两组合。
01:31
好,首先来看如果说被加数和加数都是正数的话,此时我们只需要让被加数和加数的绝对值做一个加法,那相加的结果保持为正就可以。而如果是负数+负数,那么同样的,用这两个数的绝对值做一个加法,符号位保持是负不变。如果被加数是正的,加数是负的,那么我们需要用绝对值大的一个数减掉绝对值更小的一个数,最终的符号位和绝对值大的一个数保持一致。
02:06
比如被加数它是正的14,而加数是负的15。那么我们丢给减法器这个硬件进行减法运算的时候,是把这两个数的绝对值丢给了它,并且是绝对值大的一个数,也就15作为被减数,然后减掉绝对值小的一个14作为减数,这样我们就确定了数值部分应该是1,然后最终的结果符号位应该是和绝对值更大的这个数保持一致,也就是负的。
02:38
好,最后一种情况,一个负数加上一个正数。这种情况下处理的方式和上面其实是一样的。同样是要用绝对值更大的那个数减掉绝对值更小的来确定数值部分,然后符号位和绝对值更大的那个数保持一致。原码的这种加法逻辑如果用硬件实现的话是很复杂的。
03:00
接下来我们再来看原码的减法运算。其实减法运算我们最终终归可以把它转变成与之等价的加法运算,我们只需要把减数的符号取反,这样的话我们就可以把一个减法操作转变为与之等价的一个加法操作。而转变为加法之后,就可以用我们之前提到的这个逻辑来进行处理。好,所以减法操作这儿就不再多说。另外一点需要注意的是在进行原码加法的时候,如果被加数和加数它们的符号都是相同的,那么加法之后得到的结果有可能出现溢出的情况。好,这是原码的加法和减法的一个实现思路。减法只需要把减数符号取法,就可以转变成余之等价的加法。
03:51
刚才我们说过原码的加法这么复杂的逻辑,用电路实现太难了。所以计算机当中通常是用补码来实现加减运算的,那这一点我们在之前的小节中同样提过。对补码进行加减运算,我们不需要单独的考虑符号位应该怎么处理,符号位同样参与运算就可以。
04:14
好,来看一下,现在有A和B这样的两个数,分别是15和负的24。那如果用二进制表示它们的真值,应该是这样的两个数,四个一表示的是15,然后11000表示的是24。好,现在这个机器字长是八位,其中最高位是符号位,所以我们把A和B两个数补足八位,得到它们的原码,再根据原码我们可以求得A和B两个数所对应的补码。
04:42
原码和补码的一个转换关系我们之前已经介绍过(正数相同,负数从右往左……)。好,现在我们要计算A加B的值,那我们只需要把A和B两个补码一起丢给加法器,然后加法器会让这个符号位也一起参与运算。好,这样我们就得到了A加B的一个值的补码表示。可以验证一下,我们把这个补码转变成原码,会发现它所对应的真值是-9,也就是15加上-24的一个正确结果。
05:14
那还记不记得补码如何转变成原码?和原码转变成补码的方法是一样的。在补码的基础上数值位全部取反,然后末尾加一就可以得到与之对应的原码。
05:28
其实还有一种更快速的转换方式,用我们上一小节推出的这一个规律:对于一个负数来说,负数的补码当中最右边的一个1,也就是这个一,这个1还有它的右边的部分和原码是保持一致的,而这个1左边的这些部分和反码是保持一致的,所以我们把补码转变成原码,一个更快的方法是找到补码最靠右的一个一,然后以这个一作为一个分界线(线划在这个1的左侧),这个分界线左边的这些全部取反就可以直接得到原码。当然了,用之前大家熟悉的方式来转换也是ok的。
06:15
好,刚才我们探讨的是补码的加法运算。接下来来看一下补码的减法运算如何实现。减法必然是可以转换为加法的,A减B可以把它等价为A加上-B,所以我们只需要根据B的这个补码来得到 -B的补码就可以。这个转换方式我们也强调过,只需要把所有的位包括符号位在内全部取反,然后末尾加一就可以。
06:43
当然你也可以结合我们刚才介绍的这些方法,要由B的补码求出 -B的补码。由于B是一个负数,所以我们可以先求出这个负数的一个原码。从右往左找到第一个1,这儿画一条分界线。第一个1的左边所有的部分全部取反0001,然后这个1的右半部分全部保持不变。这样的话我们就可以直接得到 -B的一个补码表示,和我们所有的位取反,然后末尾加1,得到的结果是一致的。好,那这儿我们得到的值它是一个补码表示把转换成真值的话就应该是正的39,这就是我们所期待的正确结果。
07:32
补码的加法和减法的实现最终都会转变成加法。这样的话我们只需要设计一个实现加法的硬件加法器就可以,不需要设计减法器。两个数的补码进行相加操作的时候,符号位也会参与运算。好,接下来给大家一个题,大家可以暂停来练练手。我们引入一个新的变量C,然后A和B就是我们之前给出的这两个值。大家可以试一下A加C和B减C的一个补码运算的结果,可以暂停试一下。
08:07
好,我们来看一下A加C最终计算的结果是这样的,这是C的一个补码,那这个补码所对应的原码是多少呢?我们从最右边这个一的这儿画一条竖线,然后前边这些数值位全部取反,最右边的这个一,还有它再往右的部分保持不变,符号位同样保持不变。这就是这个补码所对应的原码,大家可以自己算一下。真值是负的117。
08:40
好,现在问题发生了,A等于15,C等于124,但是计算的结果,两个正数相加竟然得到了一个负数。那这种现象说明发生了溢出,因为124加上15应该是等于139。而我们之前说过八位的补码,它所能表示的范围只有-128到127这样的一个范围,所以139这样的一个真值显然已经超出了八位补码可以表示的范围,所以这种情况下就是所谓的溢出。
09:18
好另一个例子,B减C其实也是一样的,我们通过C的补码求出负C的一个补码,这样的话就可以把B减C转变成加法运算,最终得到的这个结果可以看到,对应真值是108,同样也是发生了溢出的问题。负的24减掉124,已经超出了八位补码所能表示的范围,两个负数相加竟然得到了一个正数。
09:45
好,既然溢出的状况不可避免,因此计算机硬件就必须考虑到如何处理溢出的问题。所以接下来我们要探讨用计算机硬件实现溢出判断的一个逻辑。当我们对补码进行加法或减法运算的时候,最终都会被转变成加法运算,所以我们只需要考虑补码的加法运算如何判断溢出就可以。好,溢出分为这样的两种,一种叫上溢出,一种叫下溢出。
10:16
根据这个图很好理解,一个补码它所能表示的范围是有限的。比如八位的补码就是负的128,一直到正的127这样的一个范围。如果两个正数相加,最终的真值超出了127的范围,此时我们就称发生了上溢。那类似的如果两个负数相加,最终得到的真值小于负的128,超出了负数区所能表示的范围,那此时就发生了所谓的下溢。当发生上溢的时候,一定是两个正数相加,最终得到的结果看起来是一个负数。而发生下溢的时候,一定是两个负数相加,最终得到的结果看起来是一个正数。也就是我们刚才提到的这两种情况。
11:09
我们可以基于这个规律来设计判断溢出的逻辑。给大家一个例子,大家可以自己体会一下,这儿给出了三位的补码所能表示的范围,也就是-4到+3这样的一个范围,并且这个地方已经给出了每一个数值所对应的补码到底是多少。
11:29
首先第一点,只有两个符号相同的数相加的时候才会发生溢出,这一点应该是很好理解的。因为如果两个数,比如说一正一负进行相加操作的话,那最终得到的结果只有可能比被加数和加数的绝对值更小。所以两个异号的数相加不可能发生溢出。
11:51
好,现在结合这个三位补码来验证一下当发生上溢的时候,是否一定是正数加正数,然后最终得到一个负数,来看一下三位补码最大可以表示的正数是3,如果两个正数相加超过了三这个值,那么此时就发生了上溢。比如说2加2,那么二的补码是010,这两个数相加应该是等于00,往高位进一个一,也就是110,那这个补码对应的是负四这个值。那你会发现从这个数轴上看,在二的基础上再加2,其实相当于从二为出发点往右移动一格,然后再再移动一格,移动两格来到了负四这个位置。所以2加2发生了上溢,最终得到的结果是-4,这和我们之前给出的这个结论是相符合。
12:50
大家看如果是3加3,那么就是011加上011,得到的结果应该是110,也就是-2这个值。那在3的基础上再加3,如果结合数轴来看的话,就是从这个点出发向右移一格、两格、三格移动到了-2这个地方。同样的两个正数相加得到了一个负数,那么此时我们就可以判断是发生了上溢的情况。好,所以结合这个例子我们可以体会到,当发生上溢的时候,一定是正数加正数,最终得到的结果是一个负数。而发生下溢的时候,大家也可以自己验证一下,一定是负数加负数,最终得到的结果是一个正数。
13:40
好,那基于这个规律,计算机硬件如何判断溢出呢?先来介绍第一种方法,采用一位符号位计算机进行溢出判断的逻辑表达式是这个样子,其中AS表示的是被加数的一个正负号,而BS表示这是加数的一个正负号,而SS表示的是最终运算结果的一个正负号。当这个逻辑表达式计算结果为0的时候表示没有溢出,为1的时候表示有溢出。
14:15
好,现在我们来解释一下这个逻辑表达式的一个含义。这可能是跨考同学的一个盲点。如果大家学过离散数学,肯定学过这种逻辑表达式。除了离散数学之外,数字电路、数学这门课里边也会接触到这种逻辑表达式。不过跨考的同学可能没有学过这些内容,所以我们来解释一下,照我们AS,BS和SS表示的是逻辑值的真和假,或者说零和1。
14:44
以这给出的第一个加法为例,AS表示的是被加数的一个符号,也就是零,表示逻辑值的假。然后BS表示的是加数的一个符号,同样是零表示逻辑值的假。然后最后SS表示的是最终运算结果的一个符号位,符号位为一表示逻辑值的真,也就是这三个逻辑值分别为假假真。只不过这个地方我们把这几个逻辑值用这种连着写的方式把它们写在一起,表示的是这些逻辑值之间执行一个与的运算。那这儿的与运算其实就是大家熟悉的C语言里的且的一个操作。
15:33
几个逻辑值进行与运算的时候,只有所有的逻辑值都为真都为一的时候,最终相与的结果才是一。只要其中任何一个为假,最终都会等于假。跨考的同学可以联想一下,你编程的时候写if语句,你是不是会写某某某某条件且某某条件再且某某条件,对吧?只要其中的某一个条件不成立为假的时候,那么这个逻辑运算最终的结果肯定都是假。只有所有条件都满足都为真的时候,最终得到的结果才是真。这是与运算的一个逻辑。在这个式子当中除了把这些逻辑值并排着写之外,这还有一个加号。
16:22
但是这个加号表示的是逻辑的或运算,也就是大家编程的都会用到的或这个运算符某条件或某条件再或某条件。如果多个条件进行或运算的话,那么只有所有的这些条件都不成立,都为假,或者说都为零的时候,这个货运算的结果才是零。而只要其中一个或者多个条件能够满足为真或者说为一的话,那这个或运算的结果就是一。
16:56
最后这个式子当中还会涉及到逻辑非运算。在一个逻辑值的上面画上一横表示一个非运算。其实非运算就是大家写代码的时候会用到的是感叹号。感叹号然后某一个条件对吧?其实就是指这个条件真假取一个反,这就是非运算,它的效果就是0变1,1变0。
17:21
好,弄清楚与或非这三个基本的逻辑运算之后,来看一下这个逻辑运算的式子最终得到的结果。基于这样的第一个式子,被加数和加数的符号位都为0,然后最终得到的结果符号位为1,而最终的这个结果需要进行一个非运算,也就是会把1变成0。好,那左边这一整块的运算就是三个0进行与运算,显然这个与运算最终得到的结果是0。
17:55
再来看右半部分,本来是001,现在AS和BS会分别进行一个非运算,0会变成1,这样的话右边部分就相当于是三个1的与运算。右边这个整体运算结果是1,好,最后这两个与运算再进行一个或运算,0或1应该是等于1。所以基于刚才给出的这些加法,我们用这个逻辑判断就可以得到V的最终结果是等于1,此时表示是有溢出发生的。那第二个式子的逻辑运算,跨考的同学也可以自己代进去验证一下V同样可以得到1。当有溢出发生的时候,也就是V等于1的时候,一定是左边这个部分运算的结果等于1,或者右边这个部分运算的结果等于1。那当加数和被加数的符号位都为1,最终运算的结果符号位为0的时候,就会导致左半部分运算结果为1。而如果加数和被加数符号位都为0,最终运算的结果符号位为1,此时就会导致右半部分的运算结果为1。
19:13
其实这个式子背后的逻辑依然是我们之前提到过的东西。如果两个负数负加负最终等于正,那么这种情况下是有溢出的。如果正加正等于负,此时也是有溢出的。那既然背后的逻辑这么简单,我们为什么还要折腾写出这么复杂的一个逻辑表达式呢?原因是这样的,与或非这些逻辑运算可以很方便的用一个门电路就实现与门或门,非门就是分别实现与或非的。所以只要我们用数学的方式写出了这个逻辑表达式,其实就相当于我们已经把硬件的电路都给设计好了。好,所以这个地方折腾这么多,其实我们就是在设计硬件电路的实现,只不过我们是用数学表达式的方式把它表示出来而已。好的,这是硬件判断溢出的第一种方法。
20:13
接下来看第二种实现方法。同样只采用一位符号位,但是我们会根据数据位的进位情况,还有符号位的进位情况来进行溢出的判断。当符号位的进位为0,然后最高数值位的进位为1的时候,此时发生了上溢。而符号位进位为1,最高数值位进位为0的时候,说明此时发生了下溢。好,我们来解释一下什么叫符号位的进位和数值位的进位。
20:43
同样的我们结合这儿给出的例子。好,现在计算A和C补码相加的一个值,末位1加0等于1 ,1加0等于1,1加1等于0向高位进一个1,那1加1再加1等于1向高位进一个1,1加1等于0向高位进一个1,1加1等于零向高位再进一个1,现在我们已经算到了数值位的最高高一个位,这个位等于0加1再加1,那么应该是等于0,然后往高位进一个1,这就是所谓最高数值位的进位。这儿是进了一个1,接下来是符号位的运算,0加0再加1应该是等于1。那上一小节中我们是不是提到过进位位的一个问题?当A和C相加的时候,进位位应该是等于0,也就是这儿所谓符号位向更高位产生的一个进位CS,所以在这个例子当中,CS等于0,然后C1等于1,此时一定是发生了上溢。
21:56
第二个例子大家也可以验证一下,这两个数相加,最高数值位的进位应该是0,然后符号位更高位的进位应该是1,两个负数相加最终得到的结果是一个正数,而原本的符号位往更高位又进了一个1,此时说明发生了下溢。好,所以只要发生了溢出,那么刚才我们提到的这两个位的数值肯定是不同的,那怎么用硬件来判断不同的逻辑呢?只需要用我们之前提到的异或运算就可以。计算机硬件只需要把符号位的进位和最高数值位的进位进行一个异或运算,如果异或的结果为0,那么表示没有溢出,异或的结果为1则表示有溢出。因为之前我们说过异或的逻辑就是如果两个逻辑值不一样,那异或的结果为1。而如果两个逻辑值相同,那么异或的结果都是0,这样的话就符合之前我们给出的判断逻辑。好,这是用硬件判断溢出的第二种方法。
23:04
好,再看最后一种用硬件判断溢出的方法。我们可以采用双符号位的补码来表示一个数,正数的符号位是两个零,负数的符号位是两个一。之前我们介绍的补码都只有一个符号位,现在我们把符号位进行一个拓展,让它有两个符号位,并且两个符号位一定是00或者11。好,我们来试一下用双符号位表示的A和C进行一个相加的操作。同样的两个符号位都会参与我们的加法运算,同样要考虑进位这个问题,最终得到的结果是这个样子,也就是运算结果的两个符号位,一个为0,一个为1,此时我们可以断定是发生了上溢。
23:50
而第二个例子,两个负数用双符号位补码的形式来表示,相加之后最终得到的结果符号位为1和0,两个符号位不一样,那么此时说明发生了下溢,在最终得到的运算结果当中,更高的这个符号位表示的是本来应该得到的正确的一个正负性,而第二个符号位表示的是实际得到的一个正负的结果。所以本应得到正的,而实际得到了负的,那此时一定是上溢。本应得到负的,但是运算的结果是一个正的,此时可以判断发生了下溢。
24:30
好,所以如果要用硬件来判断溢出的话,同样的我们只需要用一个异或运算,把运算结果的两个符号位进行一个异或,如果异或的结果为0,也就是两个符号位都相同,那么说明没有溢出,异或的结果为1,说明有溢出。那这儿给出的是一个有溢出的情况,大家也可以结合之前的例子来自己动手验证一下。如果说没有发生溢出的话,那最终得到的两个符号位一定是相同的,这儿就不再赘述。
25:03
好,那我们还需要补充一个关于双符号位补码的一些边角的知识点。双符号位补码又称为模4补码,而之前我们学习的单符号位的那种补码又称为模2补码。可以这么理解,如果把双符号位的补码这个逗号把它看作是小数点的话,那么它前边这一位的权值应该是2的0次方,再前面这一位应该是二的1次方。那如果再往前还有一位的话,应该是二的2次方,也就是4。而之前我们简单的介绍过模运算,如果模4的话,其实是相当于把位权小于4的这些部分给保留,而位权大于4的这些部分全部把它舍弃。所以如果把这个地方看作是小数点的话,那由于我们只保留小数点前边的两位,所以这就相当于计算机在运算之后进行了一个模4的操作。
26:04
那单符号位被称为模2补码的原因也是类似的。如果把这个地方看作是一个小数点,那么它前边这一位的位权就应该是2的0次方。在前边如果还有别的位,那应该是2的1次方。那模2就相当于把位权小于2的这些部分给保留,而位权大于或者等于2的这些部分全部给舍弃。所以单符号位的补码又称为模2补码。
26:33
好,另外一点需要补充的是,虽然我们这儿看到的这个补码它有两个符号位,但是其实这种补码在内存里,在计算机当中进行存储,实际也只存储一个符号位,只不过在这个补码进行运算之前,在运算之前会复制一个符号位,然后接下来补码的运算会让两个符号位同时参与运算。所以这种双符号位的补码并不会增加存储所需要的空间。
27:05
好的,这就是关于溢出判断的3种方法。这个小节的内容比较多,我们介绍了定点数的加减运算怎么实现,对原码的加减运算的实现相对来说会复杂一些,所以实际当中通常使用补码的方式来实现加法或者减法。对于补码的减法操作,最终我们肯定可以把它变为等价的加法操作,并且最后符号位会参与运算,符号位也要考虑到进位相关的问题。
27:37
对补码进行加法运算有可能会导致溢出的问题。如果两个同号的数相加,最终得到结果符号改变了,那么就说明发生了溢出。超出补码正数所能表示的范围,这种现象称为上溢,超出补码负数所能表示的范围,这种现象称为下溢。
我们介绍了三种用硬件判断溢出的方法,在介绍双符号位的时候,我们也引入了两个概念,叫做模4补码和模2补码,这两个概念大家也需要注意。好的,以上就是这个小节的全部内容。