《计算机组成原理:硬件软件接口》第三章:计算机的算术运算 学习笔记

3.1概述

计算机中的字(word)是由位(bit)组成的,因此,字可以表示为二进制的数字。尽管自然数(0,1,2等)既可以用十进制的形式表示,也可以用二进制的形式表示,但是其他一些常见的数又如何表示呢。

例如:

--负数?

--计算机中的字可以表示的最大的数是多少?

--如果某一操作所产生的结果比一个字所能表示的最大的数还大,那会出现什么情况呢?

--小数和实数如何表示?

需弄清的根本原理:硬件究竟是如何进行乘法或除法运算的?

本章目的就是要揭示这些基本原理:包括

--如何表示各类数字

--用什么算法来实现算术运算

--硬件是如何实现这些算法的

--在指令集中如何表示所有这些有关的内容

3.2有符号数和无符号数

我们可以通过各种基数(base)来表示一个数。人们通常习惯于10为基数,正如我们在第2章中所介绍的那样,在计算机中则更倾向于以2为基数

最低有效位:第0位

最高有效位:第31位

MIPS机器的字长为32位,故十进制取值范围:0 ~ 4294 967 295

整数除了可以用2的补码来表示之外,我们还可以用ASCII数字字符串来表示一个数字.如果要表示的数字为10亿,那么用ASCII数字字符表示法所需要的存储空间是用32位长的二进制表示法的多少倍?

解: 10亿就是1 000 000 000,需要用10个ASCII数字来表示,每个数字长度为8位.因此,所需的存储空间是用32位长的二进制表示法的(10 x 8)/32即2.5倍。除了需要更多的存储空间以外,对于注重方法表示的数字,硬件在做加法、减法、乘法以及除法等各种运算时,所执行的操作也比二进制表示法复杂的多。正是基于这些原因,计算机专家们才一致认为,在计算机中使用二进制表示法再自然不过了。

硬件可以设计为直接对二进制位模式进行加法、减法乘法以及除法等运算。如果这些运算的正常结果无法用硬件能提供的有效位表示出来,就称这种现象为溢出(overflow)。溢出发生时,操作系统和相应的程序必须决定如何处理这种现象。

计算机程序既要处理正数,也要处理负数,因此我们必须设计一种表示方法,用以区分正数与负数。最直观的方法是增加一个单独的表示正负的符号,这只要用一个二进制位就可以方便地实现;这种表示方法称为符号加绝对值表示法,又称为原码。如今所有的计算机都用2的补码来表示带符号的整数。2的补码表示法有一个很大的优点,即所有负数的最高有效位(MSB)为1.因此,只要检查一下最高有效位,硬件就能判断某个数是正数还是负数(将0当成正数)。这个特殊的位称为符号位(sign bit)。在重新考虑了符号位的特殊情况以后,我们仍然可以用各位的值与2的相应次幂的乘积之和来表示32位正数和负数。

有符号数的溢出现象:当二进制位模式最左端保留为与保留无穷多位时左端的位不同时,就发生了溢出。

有符号数与无符号数的区别不仅表现在算术运算上,还表现在将这些数据加载入寄存器的过程中。将有符号数写入寄存器时,需要用符号位填满寄存器左侧中多余的位——我们将这一过程称为符号扩展(sign extension)——其目的是为了使这个有符号数能以正确的表示存放在寄存器中。而无符号数装入时,只需将左侧剩余的位置全部填上0即可,因为其中的数据是没有正负之分的。将32位字长的数据装入32位宽的寄存器时,有符号数和无符号数的装入过程是一样的。MIPS机器提供了两种形式的字节装入指令:一种是带符号的字节装入(load byte, lb),它将要装入的字节看成一个有符号数,并用符号扩展寄存器左侧的24个剩余的二进制位;另一种无符号的字节装入(load byte unsigned, lbu)则将要装入的字节看作无符号的整数。由于C程序中通常是用字节来表示字符类型(character),很少将其当成带符号的断整数,因此几乎所有的字节装入都是lbu。同理,带符号的半字装入(load half , lh)将半字当作有符号数处理,要在寄存器左边的16位进行符号扩展,而无符号半字装入(load half-word unsigned,lhu)处理无符号整数。

一种简便方法告诉我们如何将一个n位的二进制数转换为一个多于n位的数。例如,在取数、存数、分支、加法以及小于时置1等指令中常常包括了一个立即数字段;这个字段是一个16位长的以2的补码形式表示的有符号数,其有效值的范围从-32768(-2^15) ~32767。在将这个立即数字段与某个32位寄存器相加之前,计算机首先要将这个16位长的立即数转换成与之相等的32位二进制数。我们要介绍的简便方法就是将16位立即数的最高有效位(即符号位)填满32位中多出的那些位。而原有的位则原样填充到新字的低16位。这种方法常常被称为符号扩展

第三个简便方法是用来降低检查是否0 <= x < y的开销的,这常常用在数组越界检查中。要点是补码表示的负数看起来很像无符号数的很大的数;即在前一种表示中最高有效位是符号位而

3.3加法与减法

减法则是通过加法实现的:先将减数求反,然后再相加.前面曾提到过,当计算机的硬件(在这个例子中,硬件的字长为32位)无法表示运算的结果时,就会发生溢出现象。在加法运算中,什么时候会发生溢出呢?两个加数的符号相反时,是不可能发生溢出的。这是因为在这种情况下,和的绝对值又不会比任何一个加数的绝对值更大,因此和也一定能用32位字长表示。也就是说,正数和负数相加时,是不会发生溢出的。在减法运算中也存在类似的不会发生溢出的情况。只不过其条件恰好与加法相反:当减数和被减数的符号相同时,溢出是不可能发生的。

弄明白了在什么情况下加法和减法运算肯定不会发生溢出之后,我们还必须回答另一个问题:究竟何时会发生溢出?这个问题的答案是:对于加法而言,溢出发生在两个正数相加而结果为负时;或者两个负数相加结果为正时。显然,将两个32位的整数相加或者相减时,结果有可能需要33位才能完整地表示出来。缺少第33位意味着发生溢出时,符号位实际上被结果的数值部分占据了,而没有被设置成结果真正的符号位。由于我们所缺少的只是一个额外的数位,因此之后符号位可能是错位的。也就是说,一个进位占据了符号位。

对于减法而言,如果正数减去负数而结果为负数,或者负数减去正数而结果为正数,那么就发生了溢出。这意味着在做减法时从符号位借了1.

如果是无符号整数的话,那又会是什么情况?通常情况下,无符号整数一般都是用来表示内存地址的,溢出即使发生也大多被忽略掉了。

因此计算机设计者必须提供一种方法,有的情况下忽略溢出,而其他情况下则必须识别出它们。MIPS解决这个问题的方法是采用两种不同的算术运算指令来分别处理这两类情况:

        ·加法(add)、立即数加(addi)、以及减法(sub)指令在发生溢出时会产生异常。

        ·无符号数加法(addu)、无符号立即数加法(addiu)以及无符号数减法(subu)在发生溢出时不会产生异常。

由于C语言通常都是忽略溢出情况的,因此无论是对何种类型的变量做算数运算,MIPS上的C语言编译器都会选用无符号的算数运算指令,即addu、addiu、subu。而MIPS上的Fortran编译器则会根据操作数的不同类型来选择合适的算数运算指令。

MIPS在检测到溢出发生时,会产生一个异常(许多计算机也称之为中断)。异常(或者称中断)实质上是一种意外的程序调用。溢出发生时,造成溢出的那条指令的地址被存放到一个特定的寄存器中,然后计算机跳转到一个预先定义好的地址,去执行与该异常情况相对应的异常处理函数。MIPS中有一个名为异常程序计数器(Exception program counter, EPC)的寄存器,用来保存造成异常的那条指令的地址。指令mfc0(move from system control)可以将EPC的地址复制到某个通用寄存器中,这样只需通过一条跳转寄存器指令,程序就可以返回到造成异常的那条指令处继续执行。

3.5除法

除法是乘法的逆运算.除法出现的频率比乘法还要低,而它的硬件实现方案也比乘法"诡异".此外,在进行除法运算时,还要考虑到一种在 数学上没有意义的情况:除0操作(即除数为0)。首先还是让我们以十进制数为例,回顾一下在笔算时所用到的长除法算法以及有关的操作数的名称。类似于上一节的例子,这里的操作数的各数仍然限制为0或1.

除法运算有两个操作数:被除数与除数,其结果也分为两部分:商和余数。很少情况下,程序使用除法指令是为了求得余数,而忽略商。在此前学过的最基本的除法运算中,对于每一位,我们都必须判断出最大能从被除数中减去除数的多少倍,从而得到该位上的商。不过,对于我们精心挑选的这个例子而言,由于各位非0即1,因此要判断被除数减去除数的多少倍其实是非常简单的:要么是0倍,要么就是1倍。就二进制数而言,各个数位上也是非0即1,因此在做二进制数除法时也只有上述这两种选择;这就使得二进制的除法变得相对比较简单。

3.5.1除法算法及其硬件实现

除法硬件实际上是模仿我们很小就已经很熟悉的长除法。它首先将32位的商寄存器清零。在算法的每一次迭代中,都要将除数右移一位。因此,在初始化时,要将除数放到64位的除数寄存器的高32位;每算完一位,则将其右移一位,使之与被除数相应的部分对齐。至于余数寄存器,则被初始化成被除数的值。

****************浮点数的表示及运算方法略****************

在接下来的各节中,我们将分别介绍浮点数加法与浮点数乘法的有关算法.浮点数运算的核心是对有效数位部分做相应的整数运算,此外,还需要处理好指数的问题,并要对最后的结果进行规格化处理.我们将先给出十进制浮点数的直观算法,然后再详细地讨论二进制浮点数的相应算法,并给出相应的图示

3.6.2浮点数加法

注意位数限制问题.如果一个加数为正数,而另一个为负数的话,所得到的和可能有许多前导零,此时就需要将它向左移动一定的位数.不论是增大指数还是减少指数,我们都必须检查是否发生了上溢或者下溢.也就是说,我们必须确保和的指数的大小没有超出指数字段所能表示的范围.

首先调整指数比较小的那个加数的有效数位部分,使之与另一个加数的小数点对齐,然后再将两者相加.第三步对得到的结果进行规格化,并检查是否发生了上溢或下溢的情况.这一步中对上溢和下溢的判断是与加数所能达到的精度相关的.前面已经提到过,指数中各位皆为0的情况是专门用来表示浮点数0的;此外,如果指数中的各位皆为1,则说明这个属已经超出了普通的浮点数所能表示的最大范围;此外,如果指数中的各位皆为1,则说明这个数已经超出了普通的浮点数所能表示的最大范围.对单精度的浮点数而言,指数部分的最大值为127,最小值为-126;而对于双精度浮点数,这两个边界值分别为1023和-1022.

3.6.3浮点数乘法

我们刚介绍了浮点数加法。下面介绍浮点数的乘法运算。首先,还是让我们看一下手工计算用十进制科学记数法表示的浮点数乘法的一个例子:1.110 x 10^10 x 9.200 x 10^(-5)。不妨假设有效数位只能保存4位十进制数字,而指数字段则只能存放2位十进制数字。

第一步:与加法不同,在乘法中,只要将被乘数和乘数的指数相加即可得到乘积的指数。如果用移码表示指数的话,从原理上来说也应该得到相应的结果。10和-5的移码分别是:10 + 127 = 137以及 -5 + 127 = 122;但是,两者相加得到的新的指数移码却是:新的指数 = 127 + 122 = 259.显然,这个数字太大,已经无法用8位的指数字段表示了,因此,肯定是什么地方出了差错。其实,问题就出在偏移值上面;在将真正的指数相加的同时,偏移值被加了两遍;由此可知,若要得到用正确的译码表示的两个数之和,那么在将用移码表示的两个加数相加之后,还必须从中减去一个偏移值。

第二步:接下来要将被乘数与乘数的有效数位部分相乘:被乘数和乘数的小数点右侧都各有三位数字,因此乘积的小数点应该放在从右边数第6位有效位之前,即:10.212000.假设小数点后面只能保留3位数字,那么乘积就可以写为10.212x10^5。

第三步:这个结果仍然是未规格化的,因此还要进一步的规格化修正。也就是说,只要将上一步得到的乘积的有效数位部分右移一位,再将指数加1,就可以得到规格化的结果。此时,还应该检查结果是否发生了上溢或者下溢。当被乘数和乘数都很小的时候(即两者的指数部分都是绝对值很大的负数时),乘积就有可能下溢

第四步:我们在前面作了这样的假设:即有效数位部分(不包括符号位)只能容纳4位数字。因此,还必须对得到的乘积舍入到4位有效数字。

第五步:乘积的符号位是由被乘数和乘数的符号位共同决定的。如果两者的符号相同,乘积的符号就为正,否则的话,乘积的符号就为负。在加法运算中,和的符号位是由加数的有效数位相加的结果决定的;而乘法中则是由被乘数和乘数的符号位决定的。

二进制浮点数乘法的计算过程(如果3-18所示)与十进制的情况完全类似。首先也是要将被乘数与乘数的指数(以移码表示)相加,再减去移码偏移值,从而得到乘积的指数部分。接下来将被乘数与乘数的有效数位部分相乘,如果有必要的haul,还要进行规格化,从而得到乘积的有效数位部分。然后根据指数部分的大小判断是否发生了上溢或下溢的情况,再将乘积的有效数位舍入到限定的位数。如果舍入后仍然需要在册规格化的话,那就要再次检查指数部分,看是否发生了溢出。最后,符号位。

3.6.4MIPS的浮点指令

MIPS支持IEEE754标准定义的单精度浮点数与双精度浮点数格式,有关的指令主要有以下几条

 浮点数比较指令根据比较的条件将某一标志位设为真或假;而浮点数分支指令则根据比较条件决定是否需要跳转。

MIPS的设计者设置了独立的浮点数寄存器。这些浮点数寄存器既可以存放单精度浮点数,也可以存放双精度浮点数。与单独的浮点数寄存器相对应,MIPS还提供了两条专门用于浮点数的存储器取存(load/store)指令lwcl和swcl。浮点数据传输的基址寄存器仍然是整数寄存器。如果要将两个单精度的浮点数从存储器装入到浮点寄存器中,然后将它们相加,最后再将结果存回到寄存器中去的话,我们可以用如下的MIPS代码来完成这一任务。

3.6.5精确的算术运算

一定的字长所能表示的整数的范围总是有限的;但任何一个整数只要落在所能表示的最大值和最小值之间,就可以精确地加以表示。浮点数的情况则大不相同。有些浮点数是无法用有限的字长精确地表示出来的,这时我们一般就只能用近似值来代替。其中的原因是很显然的:我们知道,在任意两个实数(例如0和1)之间都有无穷多个实数,但是对于双精度的浮点数而言,它所能表示的实数的总个数却只有去取的2^53个而已。如果能找到与实际要表示的实数最接近的那个可以表示出来的数,那已经是很不错的了。为此,IEEE754标准为设计人员提供了若干种舍入的方法,用以选择一个最合适的近似值。乍看起来,舍入似乎是相当简单的一件事情。但是为了能准确地进行舍入,硬件在进行浮点运算时就必须额外多保留几位。在前面的例子中,我们很难说究竟需要用多少为来保存中间结果才比较合适;但是,可以肯定的是,如果中间结果的位数和最终结果的位数完全一样的话,我们就不会有任何余地来进行舍入了。因此,IEEE754标准规定,所有浮点运算的中间结果的右边都必须额外多保留两位,这两位分别叫做保护位和舍入位。

如果某个浮点数恰巧等于相邻的两个可表示浮点数的平均值,那么此时进行舍入对精度的影响将试最大的。浮点数的精度通常可以用近似值与实际值的最低有效位中不同位数的数目来表示,这个数目叫做末位单位,简称ulp。如果近似值的最低有效位比实际值小2,那么我们就说误差为2ulp。在没有发生上溢、下溢以及因非法操作而发生异常的情况下,IEEE754标准可以确保将浮点运算的精度控制在1/2个ulp的范围之内。

细节:在上面这个加法的例子中,我们上只需要一个额外的保护位就够了。但是,在进行浮点乘法运算时,两个额外数位则是必不可少的。这是因为二进制乘法所得到的乘积可能有一个前导0;因而在进行规格化时,就必须将整个乘积左移移位,原来的保护位就变成了乘积的最低有效位。此时,舍入位就可以发挥作用,保证最后的精度不会因此而受到不利的影响。

IEEE754标准规定:如果中途保存的最低有效位是奇数则加1,如果是偶数则截取。这种方法在两数之间的中点情况下总是在最低有效位产生一个0。额外舍入位的目的是使得计算机能够取得以无限精度求出后再舍入的同样效果,为了支持这一目标并舍入到最近的数字,此标准在警戒位和舍入位之外增加了第三位;只要舍入位的右边有非零为,它就被设为1.这一粘滞位

3.6.6总结

3.8谬误和陷阱

正常的算术运算是没有精度限制的,但是,在计算机中由于字长的限制,所有的算术运算都被限制在了一定的精度范围之内。这种限制就使得计算机中的算术运算有时候会产生一些缺陷;而如果我们没有意识到这一点的话,也就会产生一些错位的看法。

谬误1:浮点数加法满足结合率。

浮点数可以表示的实数范围是很大的。当两个绝对值非常大的符号相反的实数相加,然后再加上一个绝对值比较小的实数时,这种说法是不成立的。例如:

 由于浮点数所能表示的实数的精度是有限的,实数运算的结果也只是近似值而已。如果浮点数加法的顺序不同,其结果也可能不同。

谬误2:对于整数而言,左移指令相当于将原数与2的幂次方相乘;右移指令则相当于将原来的整数除以2的幂次方。

乍看起来,将x右移n位确实相当于把它与2的n次幂相除。对于无符号数而言,这倒也确实没错。但问题就出在带符号整数上。我们不妨举一个例子。假设要将-5除以4,正确结果应该是-1.

 解决这个矛盾的方法就是使用算术右移指令。算术右移指令在右移时会自动进行符号扩展,而不是简单地将空出来的最高位填0.

陷阱1:MIPS指令集中的addiu指令(与无符号立即数相加)会对指令中的16位立即数字段进行符号扩展。

在不考虑溢出的情况下,addiu的作用是将一个常数与一个带符号的整数相加,这似乎与其名字有点不太一致。MIPS指令集中没有给出立即数相减的指令,这就必须对负数进行符号扩展以后再由addiu指令来实现,因此MIPS体系结构决定对指令中的立即数字段进行符号扩展。

谬误3:只有理论数学加才需要关注浮点数的精度问题。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值