【计算机组成原理】2.3.2 浮点数的加减运算

2.3.2 浮点数的加减运算

00:00

各位同学大家好。通过之前几个小节的学习,我们已经知道了浮点数在计算机里边如何表示,它的表示规则是什么。那基于浮点数的这个表示规则和原理,又要如何实现浮点数的运算呢?所以这个小节我们要探讨的是浮点数如何实现加减运算。除了加减运算的实现之外,我们还会探讨浮点数还有定点数之间的一个强制类型转换的问题。

00:24

好,首先来看加减运算怎么实现,分为这样的几个步骤,对阶、尾数加减、规格化、舍入,还有溢出的判断。好,之前我们说过计算机里的浮点数和我们十进制科学计数法有很多相通的地方。所以我们同样还是从大家熟悉的十进制科学计数法出发,来理解它这些一个一个的步骤是什么意思。

00:48

好,来看这样的一个例子,有两个十进制科学计数法表示的数。好,这是一个疯狂的暗示,意味着大家上了985211之后,最终找到工作肯定也是996,甚至是007,就是每天零点开始上到第二天的零点,每周上七天,这是007,比996还要可怕。好,现在这两个数它们的阶数是不一样的,一个是十的10次方,一个是十的12次方。所以我们不能直接把这两个尾数相加,我们需要先让他们俩的阶数对齐,保持阶数一样。通常来说我们采取的策略是阶数更小的向阶数更大的对齐。所以就是要把右边这个数把它转换为十的12次方这样的一个形式。科学计数法大家都很熟悉,只需要把小数点往前移两位,然后这个阶数加2就可以。

01:43

好,大家可以思考一下,为什么是阶数更小的向阶数更大的对齐,而不是逆过来呢?可以思考一下这个问题,如果用计算机处理,那这种处理的策略可以带来的好处是什么?原因是这样的,我们在计算机内部尾数一定是一个定点的小数,小数点的位置是固定不变的,所以站在计算机硬件的角度来看,如果我们是要让这个阶数更高的向阶数更低的对齐,也就是把这个数变成985.211乘以10的10次方。如果是这么做的话,那就意味着这个小数点的前边会有好几个有效值有效位。所以如果用计算机来处理二进制的浮点数,你想让它的尾数的小数点固定在好几个有效值的后面,这个是很难实现的,不太方便用计算机处理。而如果说是让接数小的向阶数更大的对齐,那用计算机硬件做这种处理是很简单的,只需要做一个算术右移就可以。所以这就是为什么我们会规定要用阶数更小的向阶数更大的靠齐的一个原因,是为了方便让计算机对尾数进行处理。

02:58

好,接下来第二步,当我们这个阶数对齐之后,我们就可以用两个数的尾数进行一个相加或者相减的操作。具体是加是减,你看这个符号就行了。上面这个例子我们是要进行加法,这两个数进行一个相加得到的结果是这样的,相加之后这个阶数保持不变。

03:18

好,接下来第三步,第三步是要进行规格化。所谓规格化就是要保证我们尾数的第一个数值位是一个有效位。对于十进制科学计数法来说,我们只需要保证小数点前边这一位不是零就可以。所以像刚才我们举的这个例子,我们是不需要进行规格化的,那什么时候会需要规格化呢?比如说如果尾数加减之后,出现了类似于0.00啥啥啥这样的一个情况,那么我们就需要进行左归。所谓左归就是小数点固定不变,然后尾数算数左移,并且阶码减1,用这样的方式把我们的数值为最高位调整为一个非零的值,这是站在十进制科学计数法的角度来理解的左归。如果说尾数加减之后,是不是也有可能会出现类似于这样的情况,就是小数点前边出现了两位两个非零的位,那这种情况下我们就需要进行右归,也就是让小数点固定不变,尾数右移,然后阶码的值加1,就是要让小数点移到这个位置,这样才符合我们对科学计数法的一个要求。好,所以在尾数加减之后,我们需要关注到是否需要对尾数进行规格化。

04:37

好,接下来第四步叫舍入。因为在我们计算机内部,浮点数的尾数的比特位是有限的,长度有限。所以当我们把两个尾数进行相加之后,新得到的这个尾数有可能长度过长。我们必须舍弃掉某些低位部分,而舍弃一些低位部分之后,我们有可能需要要向高位进一个1,也就是所谓四舍五入之类的一个处理。好,所以这就是为什么我们需要进行舍入的原因。因为浮点数的尾数它的位数是有限的。

05:11

好,现在我们先来类比这个十进制科学计数法。假设我们规定我们只保留六位有效的尾数,那我们可以采取这样的策略,尾数当中保留六个有效值,然后超出六位的低位部分我们直接把它砍掉。当然我们也可以这么做,就是如果我们砍掉的这些部分并不是全0,那么我们会向高位进一个1,所以这一位我们从1变成了2。好,这也是一种策略。

05:41

当然了还有一个大家最熟悉的策略,就是四舍五入的策略。如果说我们舍弃的这一位已经满5,那么我们就向高位入一个1。所以这就是为什么叫舍和入的原因,我们需要舍弃一部分尾数,然后向高位入一个什么值,比如说入一个一或者入一个零之类的那通过这个部分我们能够感受到舍入这一步,我们其实可以制定不一样的这种规则。对于二进制的尾数舍入的规则有哪些,这个我们之后会再来介绍。

06:14

好,接下来最后一步是判断溢出。对于计算机来说,一个浮点数它的阶码的位数一定是固定不变的。所以如果我们对两个浮点数进行加法或者减法运算之后,导致阶码部分的值已经超出了这个浮点数阶码所能表示的范围,那么这种情况下就说明发生了溢出。

06:36

那同样的,我们还是先用这个十进制来进行一个类比。如果我们规定十进制的科学计数法当中,它的阶码不能超过两位。那么假设是这样的两个数进行相加,它们原本的阶数都是99。那这两个数相加之后是19.81218乘以10的99次方。

06:58

好,尾数相加之后,我们是不还需要进行规格化和舍入,对吧?显然这个数的规格化,我们需要对尾数进行右移的一个操作,尾数右移,然后阶码加一,那阶码就会从99变成100。而刚才我们说过,我们会规定阶码的位数不能超过两位,现在100这个数必须用三个十进制位才能表示,所以这个数就超出了我们规定的这个条件下所能表示的最大范围,此时就是发生了溢出。当我们在对浮点数进行溢出判断的时候,只有阶码溢出才是真正的溢出。尾数的溢出未必会导致整体溢出,因为尾数发生了溢出之后,我们还可以通过规格化这样的一个步骤来进行拯救。

07:47

好,所以这就是基于十进制科学计数法的对阶,尾数加减、规格化、舍入还有溢出判断这样的几个步骤所需要做的事情。大家可以先暂停消化一下,接下来我们要介绍的浮点数的加减运算,这些步骤和我们这儿介绍的科学计数法都是相通的。所以大家在学习这些新内容的时候,如果能够把新内容和自己熟悉的老内容进行一个连接串联,那你的理解可以更深也更简单,并且记忆也会更牢。

08:21

好,接下来我们进入这个浮点数,二进制浮点数的加减运算。这给出了两个十进制数,-256分之-5,还有Y这个数是1024分之59。然后让我们用机器补码的浮点运算来计算X减Y的一个值。

08:41

好,首先我们需要把这个十进制的真值,把它转换成浮点数的形式。它这已经告诉我们浮点数的格式是阶符取两位阶码取三位,数符取两位,尾数取九位。这并不是我们上一小节介绍的754标准。考试当中如果让大家模拟手算这种浮点数的加减运算的话,基本上不太可能让你直接使用IEEE754的这个标准。因为这个标准里边最短的浮点数float型都至少会有32个比特位。那你想一下,你在考卷上答题,一个浮点数占32位,那老师改卷也很麻烦。所以基本上考试里边会像这个题目这样,给你一个比较短的这种浮点数的格式。

09:29

好,来看一下我们之前说过浮点数的加减运算分为这样的几步,但是由于题目只给出了十进制的真值,所以我们需要增加一个第0步,要把真值转换成二进制数。首先看X它的分子部分5就应该是101,那分母部分256分之1,那X是等于 -5乘以256分之1,所以如果写成二进制的话,就应该是 -101乘以2-8次方,相当于把101这个数的小数点往前移八位。

10:12

那这个题目介绍的这种浮点数尾数部分,显然它并不会隐含着最高位1,所以这地方我们需要把尾数转变成零点啥这样的一个格式。这相当于对尾数进行了三次的算术右移,那每右移一次阶码应该加1,所以阶码应该从-8变成 -5。现在我们再把这个阶码转变成二进制的形式,也就是 -101。好,这是X用二进制的一个表示,我们已经推出了尾数还有阶码的一个二进制真值表示形式,一会儿我们还需要把它转变成补码。

10:50

好,现在我们先来看Y这个变量。Y的分子是59,转变成二进制,应该是这样的一个数,大家可以自己试一下,分母的话就是1 024分之1。就是2-10,所以Y应该等于正的这样一个值乘以2-10。

11:07

同样的,由于尾数部分保留的其实是一个定点小数,所以我们需要把这个尾数把它转变成零点啥啥这样的一个格式。小数点向前移了六位,所以这个阶码应该加6,从-10变成 -4。好,这是Y这个值。现在我们要把X和Y的尾数和阶码部分分别都转变成补码。好,首先是X的阶码,-101把它转变成补码的话应该是1011对吧?因为-101它对应的原码就应该是1101。那负数原码转变成补码就是这些数值位全部取反,再在末尾加1,所以就可以得到1011这样的一个值。

11:52

当然了,不知大家还记不记得,我们还介绍过一个更快的转换方法,就是在原码的基础上找到从右往左看的第一个一,在这个一的左边画一条分界线,那这个分界线的右边这些值保持不变,分界线左边这些值全部取反,1变成0,0变成1,所以就是1011。好,这是一个更快的方法。各种码之间的转换是很高频的考点,所以大家需要不断的巩固和回忆。好,这是X的阶码的补码表示,需要注意审题。由于阶符是两位,阶码是三位,所以我们还需要再增添一个符号位,也就是采用双符号位补码的形式来对应上题目要求的这个阶码的格式。

12:38

接下来再来看尾数,这是一个定点小数,它所对应的原码应该是1.101,对吧?那把原码转变成补码的话,就是这儿画一条线,然后左边这两位取反,所以就应该是1.011。尾数部分的数符需要取两位,所以我们先把它拓展为双符号位的补码,然后尾数需要取九位,所以我们需要进行一个符号拓展。还记不记得符号拓展?对于一个定点小数的补码来说,当我们要扩展它的长度的时候,只需要在末尾添0就可以凑足九位。所以除了各种码之间的转换之外,之前提到的符号扩展也是经常会被考察的,忘了的同学可以再回去看我们之前讲过的内容。

13:24

好,所以这样我们就得到了X这个数,它的阶码部分,还有尾数部分的一个具体的二进制表示。我们把它拼起来就可以得到一个完整的浮点数。那对于Y这个数的转换就留给大家自己下去练习,原理都是一模一样的。好,现在我们已经得到了X和Y的一个浮点数表示,我们要对他们俩进行相减的操作,接下来我们需要让两个浮点数的阶数对齐,并且之前我们说过应该是小阶向大阶看齐。

14:00

那计算机是如何判断谁的阶数更小的呢?很简单,只需要把两个数的一个阶数进行一个相减的操作就可以,也就是用X的阶数减掉Y的阶数。我们说过计算机里减法都是用加法实现的。所以X的阶数要减掉Y的阶数,我们只需要求出Y的阶数的负值的一个补码就可以,就是所有的位包括符号位在内全部取反,然后末尾加1。所以Y的阶码的负数的一个补码就应该是00100,所有的位取反末位加一。

14:38

总之这一步我们求出了X的阶数和Y的阶数之差,那这个差值是一个负数,把它转换成十进制的话,应该是对应负一这样的一个值。注意这是一个补码,所以你需要把这个补码转换成原码,两个符号位末位全部取反再加一,也就是001,这是差值的原码表示,就是对应负一这样的一个值。

15:02

好,那负一说明X的阶数要比Y的阶数更小,并且小多少呢?小一个值。所以我们需要让阶数更小的X它的尾数向右移一位,因为他们的阶差只有一,右移一位,并且X的阶码在原有的基础上加1,这样的话我们就可以让X和Y的阶数进行一个对齐。那如果用二进制真值来表示的话,相当于就是把这个数变成了0.0101,然后乘以2的负100次方。

15:37

好,阶数对齐之后,我们就可以对尾数进行加减。由于我们这儿是要计算X减Y,所以为了实现减法,我们需要把Y的这个尾数变成它的一个负值的补码,也就是包括符号位在内所后的这些位全部取反,末尾加一,所以这就得到了负Y的一个补码表示,因此X减Y就等于X加上负Y,那刚才我们已经得到了负Y的一个尾数,所以就让它们相加,加得的结果应该是这样的一个值。那注意这个地方一加一应该是等于0,并且往高位进1,然后高位的一加上一再加上刚才进位进上来的一就应该是等于1,然后再往高位进一个1,只不过最高的这一位会被我们抛弃。

16:23

这是双符号位补码运算的一个规则,之前我们介绍过。现在值得注意的是这两个双符号位它们不一样,所以说明尾数发生了一个溢出,只不过这个溢出是可以拯救的,那如果说结合我们之前的这个二进制真值表示的话,X减Y经过之前的对阶,然后尾数相减的操作,这步相减之后可以得到尾数的值已经大于一了。由于定点小数没办法表示大于一的一个值,所以这儿发生了溢出。大家可以结合我们手算的过程,对这个溢出的原因进行一个理解和分析。

17:00

好,现在我们得到了尾数之后,接下来应该进入第三步,也就是规格化。尾数采用双符号位补码的一个好处就是我们可以通过右归的操作来拯救刚才的这溢出。这个尾数经过算术右移之后,最末尾的这个零会被我们抛弃,然后之前小数点前的这个零会被移到小数点后面这个位置,然后高位会有一个空位,高位补多少呢?具体得看我们的这个双符号位的更高位。因为双符号位的更高位表示的是正确的,我们本应该得到的那个符号。所以根据这个信息我们可以知道我们应该补1。

17:42

那这样的话尾数完成了算术右移,尾数算术右移之后,还需要把阶码加一好,所以阶码加一之后得到了这样的一个值。好,这样我们就完成了规格化。

17:53

如果说对比手算的话,相当于我们进行了一个这样的运算。所以阶码加一就从负四变成了负三。好,接下来进入第四步舍入。只不过大家会发现刚才我们进行算术右移的时候,我们已经抛弃了一个最低位,最低位是0。而抛弃零之后并不会对整个浮点数的精度造成任何影响。所以在这个例子当中,我们不需要考虑所谓舍入的问题。一会儿我们再来补充一个需要考虑舍入的例子。

18:25

好,现在我们进入第五步,溢出的判断。我们需要判断这个阶码它是否越界。阶码也是采用双符号位补码的,所以我们只需要判断这个阶码加一之后,它的两个符号位是否相同。在这个例子当中,两个符号位相同,所以说明没有溢出,最后我们就得到了X减Y的一个真值。把阶码翻译成十进制应该是-3,然后把尾数部分用二进制表示,应该是对应这样的一个值。这个值乘以2的负3次方可以理解为在这个值的基础上把小数点往前移三位。好,所以这就是X减Y的一个过程。

19:07

接下来我们再来看一个需要舍入的例子。在我们舍去某一个低位的二进制数之后,我们通常可以采取这样的两个策略。一种是零舍一入法,就有点类似于十进制的四舍五入。另一种方法是恒置一法。

19:25

好,首先来看什么叫0舍1入法。假设我们现在已经对某两个浮点数进行对接,然后尾数加减的操作。加减之后的结果是这样的,现在由于尾数的两个符号位不一样,所以我们需要对这个尾数进行一个规格化,我们需要进行右归,也就是尾数整体右移,然后阶码加一这样的一个操作。好,尾数整体右移之后,最末位的原本的这个一是不是被我们抛弃了,所以按照零舍一入的策略,当我们抛弃的这个数值为一的时候,我们需要在剩余的这个尾数末尾再加一个一,也就是这个地方再加一个一,那一加一等于0,再向高位进一个一,所以加一之后得到的结果就是01。

20:12

那如果换一种情况,刚才被我们舍弃的这位,假如它是零的话,那我们直接把舍弃就可以。所以这就是零舍一入法。当我们舍弃的这一位是一的时候,我们需要给尾数的末尾加一个一,给尾数末尾加一这个动作有可能又会导致尾数有新的溢出,此时我们需要再进行一次右归,什么意思呢?

20:35

假设刚才这个例子当中,尾数的数值部分全部是一,那么当我们把末位的一舍弃之后,再给剩下的尾数末尾加1,那这个加一是不是会导致不断的会往前有进位,对吧?那这样的话有可能会导致我们的尾数又会有新的溢出。当发生新的移出的时候,我们又需要再进行一次右归。这是零舍一入法。

21:01

再来看第二种方法,恒置1法。不论我们舍弃的是什么值,反正我们只需要把这个剩余的尾数部分末位把它变为一就可以。好,所以这就是第二种舍入的策略,恒置1法。

当我们进行规格化和舍入之后,会导致阶码的值发生改变。那如果说阶码的值超出上限,那就说明发生了某一种上溢,具体是正上溢还是负上溢,这得看尾数的数字是正还是负。而如果阶码的数值低于它所能表示的下限,那这个时候就发生了所谓的下溢。之前我们说过下溢这种情况,我们直接把它当做机器数零就可以。发生下溢的时候,我们并不会把它当做一种错误来处理。而如果发生上溢的话,那我们必须抛出一个系统异常,或者说中断,阶码上溢才是我们必须要处理的错误。好,所以这是最后的一步,判断溢出。

21:58

刚才这个例子当中大家会发现,其实我们在进行规格化的时候,就会遇到所谓舍入的一个问题。除了规格化的时候会面临这个问题之外,有的计算机它在进行浮点数运算的时候有可能是这么做的:它会把尾数部分单独的拆分出去。比如一个float型变量,它的尾数实际有效数值应该是24个比特,对吧?也就是一个隐含的1加上23个显示表示的尾数,总共24个比的计算机有可能会把这24个比特把它拆出去,把这个尾数用一个32比特的变量来保存。这样的话当我们对尾数进行对接的时候,是不是尾数右移不会导致这个尾数的精度丢失。因为我们可以用多余的那些比特位来保留。

22:52

那用32比特的存储空间进行完对接加减运算还有规格化之后,我们是不是还需要把这32比特把它重新截断为24比特,然后再把它拼接到float浮点数里边。因此如果采用这种策略的话,当我们把这个长的尾数截断为短的尾数的时候,同样也会面临舍入的问题。好,这就是浮点数加减运算的一个实现,各个步骤大家再回忆一下,接下来我们要探讨这个小节的第二个问题,就是浮点数的强制类型转换。我们在C语言里面经常会遇到这样的一些数据类型,当机器字长系统的位数不一样的时候,各种变量的比特位也是会呈现出一些区别。现在大家使用的电脑肯定都是64位的机器,但是由于我们计组这门课,很多教材编写的年代比较久远,在他们编写的年代大部分都是32位的机器,所以考试里边他在考察这个强制类型转换的时候,通常会按照32位机器的这种变量的长度来进行考察。

24:00

最关键的一个区别就是int型和long型。在32位的情况下,long型也是占32位的,而double型双精度浮点一定是64位。其中有一位符号,然后11位的那个码加上剩下52位的尾数,那这52位的尾数再加上隐含的一个1,实际double类型,它可以表示的尾数有效数值位应该是53位53个比特。好,所以如果我们按照这样的方式来进行强制类型转换,那么这儿给出的转换都是无损的转换,数据的精度不会丢失,数据的表示范围也不会缩小。char型向int型转换很简单,因为char型我们可以把它理解为是8位的一个整数,而int型是32位的整数。long型和int型一样,它也是32位的整数。所以前面这几步的转换是无损的转,这很好理解。

24:58

好,再来看long型向double型。刚才我们说过long型这个地方我们是默认它有32个比特位,而double型的尾数实际上有53位,就是有一位隐含了1,这个我们刚才说过,那尾数有53位,它肯定能够表示32位的数所能表示的任何一个精度,对吧?所以由32位的浪型转变成double型。由于double型的尾数更长,所以精度不会丢失,所以long型向double型的强制类型转换是不会丢失精度的。当然如果题目告诉你说long形变量占64个比特64位,那么这个时候浪型向double型转变就会有精度的损失。53位的尾数肯定表示不了64位所能表示的那么多的精度。那王道书的正文是说这样的转换没有经过的损失,其实他在背后是默认了这个long型只有32位。

25:56

接下来float型向double型的转变不会损失进度,这是显然的。Float型它的尾数应该是1加23位,这的1是一个隐含的位。然后double型是1加52位,那显然精度不会丢失。另外double型的阶码是11位,而float型的阶码是8位。所以float型能表示的数,double型肯定都能表示。好,所以我们上边给出的这些强制类型转换,不管是数的表示范围还是精度都是没有损失的,是无损的转换。

26:31

接下来再来看一个考试当中很常考的,32位的int向float来转换。Int型变量表示的是一个定点的整数,它有一个符号位,然后31个有效的数值位。而flat型变量是一个符号位,加上八个阶码,再加上23位的这个尾数。那23位尾数之前还会有一个隐含的1,所以float型变量它的尾数实际有效的数值应该是24个位,那显然用24位肯定没办法表示31位所能表示的精度,因此int型转float型肯定会有精度的损失。那考虑上float型的8位阶码,它所能表示的这个数的范围肯定要比int型更大,所以int型向float型转换只会损失精度,但是数字的范围并不会出现溢出。现在我们再把它逆过来float向int转换,那么既有可能发生溢出,也有可能损失精度。发生溢出很好理解,因为fat型它能表示的范围已经超出了int型所能表示的范围。

27:42

那什么情况下会损失精度呢?我们float型变量它有可能会表示一个小数,对吧?比如它可以表示0.000111,可以表示这样的一个小数。那这个小数在转变成int型变量的时候,它会把末尾全部截断,也就是转变成整数的0。好,所以在这种情况下,float型转int型就会有精度的损失。好,这就是浮点数的强制类型转换。

28:13

好的,这一小节中我们学习了浮点数的加减运算,还有C语言里边强制类型转换的问题。浮点数的加减运算分为对阶、尾数加减、规格化、舍入,还有溢出判断这样的几个步骤。我们在做题的时候,通常还会需要在前边再加上一个真值转换成机器数这样的一个步骤。那大家需要注意审题,阶码和尾数它到底是用原码、补码还是移码,用什么码来表示,另外也需要注意符号扩展的问题,我们需要把阶码和尾数的位数,把它们的位数扩展为题目要求的格式。

28:49

当我们写出了两个机器数之后,就需要进行对阶,对阶的方法是小阶向大阶看齐。由于计算机内部尾数的位数是有限的,所以对阶的这些操作有可能会导致末位的精度有丢失。好在阶数对齐之后,就可以对尾数进行一个加减。我们通常会采用双符号位来表示尾数。因为采用双符号位的话,当我们尾数加法或者减法发生溢出的时候,我们可以进行一个拯救,我们可以通过右归的方式来进行拯救。

29:22

好,确定了尾数之后,接下来我们就需要进行规格化。左归、右归的方法我们在之前的小节中有过详细的介绍。然后接下来由于尾数的位数有限,所以我们需要进行舍入的一个操作,常用的方法是0舍1入或者恒置1。然后最后我们需要对阶码进行一个溢出的判断,如果阶码发生了上溢,超出了它所能表示的最大的正数,那么此时我们必须抛出一个异常,说明此时发生了正上溢或者负上溢,而如果阶码发生了下溢,也就是超出了阶码负数所能表示的最小值,这种时候我们只需要把运算结果按照机器零来处理就可以。最后我们介绍了和浮点数相关的一些强制类型转换,上边这些是无损的转换,下边这些是有损的转换。好的,以上就是这个小节的全部内容。

你可能感兴趣的:(考研,学习方法,面试)