在计算机系统的发展过程中,曾经提出过多种方法表达实数,典型的比如定点数。在定点数表达方式中,小数点位置固定,而计算机字长有限,所以定点数无法表达很大和很小的实数。最终,计算机科学发展出了表达范围更大的表达方式——浮点数,浮点数也是对实数的一种近似表达。
1.浮点数表达方式
我们知道任何一个R 进制数N 均可用下面的形式表示:N R =±S ×R ±e
其中,S—尾数,代表N 的有效数字;
R—基值,通常取2、8、16;e—阶码,代表N 的小数点的实际位置(相当于数学中的指数)。
比如一个十进制数的浮点表达1.2345×102,其中1.2345为尾数,10为基数,2为阶码。一个二进制数的浮点表达0.001001×25,0.001001为尾数,2为基数,5为阶码;同时0.001001×25也可以表示成0.100100×23,0.100100为尾数,2为基数,3为阶码。浮点数就是利用阶码e 的变化达到浮动小数点的效果,从而灵活地表达更大范围的实数。
2.浮点数的规格化
一个数用浮点表示时,存在两个问题:一是如何尽可能多得保留有效数字;二是如何保证浮点表示的唯一。
对于数0.001001×25,可以表示成0.100100×23、0.00001001×27等等,所以对于同一个数,浮点有多种表示(也就是不能唯一表示)。另外,如果规定尾数的位数为6位,则0.00001001×27会丢掉有效数字,变成0.000010×27。因此在计算机中,浮点数通常采用规格化表示方法。
当浮点数的基数R 为2,即采用二进制数时,规格化尾数的定义为:1/2<=|S|<1。若尾数采用原码(1位符号位+n 位数值)表示,[S]原=S f S 1S 2S 3…S n (S f 为符号位的数符),则满足S 1=1的数称为规格化数。即当尾数的最高有效位S 1=1,[S]原=S f 1S 2S 3…S n ,表示该浮点数为规格化数。对0.001001×25进行规格化后,表示为0.100100×23。
3.浮点数的表示范围
求浮点数的表示范围,实质是求浮点数所能表示的最小负数、最大负数、最小正数和最大正数。
图1浮点数的表示范围
由图可见,0以及处于最大负数到最小负数之间(负数区)、最小正数到最大正数之间(正数区)的数为浮点数所能正确表达的数;处于最大负数和最小正数之间(下溢区)的浮点数,由于其绝对值小于机器可表示的数值,在计算机中通常作为0来处理,称为机器0;大于最大正数或小于最小负数(上溢区)的浮点数由于其绝对值大于机器所能表示的数值,因此计算机将进行溢出处理。
4.IEEE754浮点数标准
由于不同机器所选用的基数、尾数位和阶码位长度不同,因此对浮点数的表示有很大的差别,这不利于软件在不同计算机之间的移植。为此,美国IEEE提出了一个从系统结构角度支持浮点数的表示方法,称为IEEE754标准,当今流行的计算机几乎都采用了这一标准。
在IEEE754标准中,每个浮点数均由三部分组成:符号位S、指数部分E 和尾数部分M。如下图所示。
图2IEEE754标准
IEEE754浮点数据编码标准中,32位单精度浮点数表示格式如下图所示。浮点数的基数什么意思
图3IEEE75标准中32位单精度浮点数表示格式
各部分规定如下。
S:数符,0表示正,1表示负。
E:指数,即阶码部分。其中包括1位阶符+7位数值。采用移127码,移码值为127。
M:尾数共23位,用规格化表示。由于尾数采用规格化表示,所以IEEE754标准约定在小数点左边有一位隐含位1,从而使尾数的实际有效位为24位,即尾数的有效值为1.M。
根据上述规定可知,32位的单精度浮点数所表示的数值N为:
N=−1S×1.M×2E−127
IEEE754标准32位单精度浮点数N的解释如下:
若E=0,且M=0,则N为0。
若E=0,且M!=0,则N=(-1)S×2-126×(0.M),为非规格化数。
若1<=E<=254,则N=(-1)S×2E-127×(1.M),为规格化数。
若E=255,且M!=0,则N=NaN(非数值)。
若E=255,且M=0,则N=(-1)S×∞(无穷大)。
IEEE754标准使0有了精确表示,同时也明确地表示了无穷大。当a/0(a!=0)时得到的结果为+/-∞;当0/0时得到的结果为NaN。对于绝对值较小的数,为了避免下溢而损失精度,允许采用比最小规格化数还要小的非规格化数来表示。应该注意的是,非规格化数和正、负0的尾数M的前隐含位的值是0,不是1。
5.浮点数的精度问题
单精度浮点数以有限的32bit长度来反映无限的实数集合,因此大多数情况下都是一个近似值。
我们知道单精度浮点数的尾数是24位(包括其中的一位隐藏位)存储的。
lg224=7.22
由上计算,单精浮点数可以保证7位十进制有效数字。
6.浮点加减运算
设有两个浮点数x和y,它们的规格化表示分别为:
x=M x×2Ex
y=M y×2Ey
其中E x和E y分别为x和y的阶码,M x、M y分别为x和y的尾数,则浮点数的加减运算可表示为:
x±y=2Ex×M x±2Ey×M y=M x×2Ex−Ey±M y×2Ey Ex≤Ey
M x±M y×2Ey−Ex×2Ex(Ex>Ey)之所以要这样分类,是因为要遵循“小阶向大阶靠拢”的对阶原则,下面会有详细的解释。根据公式,可总结出浮点数运算的几个步骤:
1)0操作数检查
浮点数的运算过程比较复杂,如果能判断出两个操作数中有一个为0,那么运算结果马上可知,而不必进行后续的一系列操作,以节省运算时间。
2)对阶
两个浮点数相加减,首先要看它们的阶码是否相同,即小数点位置是否对齐。如果阶码相同,则表示
小数点位置是对齐的,尾数就可以直接进行加减运算;反之若两数阶码不同,则表示小数点位置没有对齐,不能直接进行加减运算。此时,必须通过“对阶”过程使两数的阶码相同,也就是使两数的小数点位置对齐。
要对阶就要改变两数中一个数的阶码,表面上看来改变哪一个都可以。由于随着阶码的改变,尾数也要做相应的移动才能使浮点数据的值保持不变,所以如果阶码变大,尾数要右移;阶码变小,尾数要左移。尾数的左右移都会造成有效数据的移出与丢失,但是右移丢失的是最低有效位,而左移丢失的却是最高有效位。显然,右移更能减小数据误差。所以,对阶必须遵循“小阶向大阶靠拢”的原则。两数中阶码较小的那个数的阶码要变大,变成和另一个数的阶码一样,而这个数的尾数要作相应的右移,右移多少取决于阶码变大多少。阶码每增加1,尾数要相应右移1位,相当于小数点左移1位。
对阶时,一般首先求出两数阶码之差,即
△E=Ex–Ey
如果△E=0,说明两数阶码相等,无需对阶;如果△E>0,表示Ex>Ey,Ey 要向Ex靠拢,其尾数M y要做相应右移;如果△E<0,表示Ex<Ey,Ex要向Ey 靠拢,其尾数M x要做相应右移。
3)尾数相加
对阶完成后,表示两数的小数点已经对齐,可以直接进行尾数的加减运算。无论加法运算还是减法运算,都与定点数补码运算一样,按加法进行操作。要注意的是,相加过程中没有溢出,也就是对于定点数来说是溢出的结果,对于浮点数尾数相加来说是很正常的事情,所以我们常用双符号位表示尾数。
4)结果规格化
尾数相加完成之后,还需要进行规格化的判定,如果不满足规格化要求,则要对结果作规格化处理。尾数的规格化处理有两种情况:
如果尾数相加结果的两个符号位数据不相等,表明运算结果的尾数的绝对值大于1,因此要“向右规格化”。由于尾数相加的绝对值不可能超过2,因此向右规格化肯定是尾数右移1位,阶码加1。
如果尾数相加结果的符号位与数据最高位相等,表示数据没有规格化,尾数要“向左规格化”,即尾数左移n位,阶码相应减n。
5)舍入处理
在对阶和向右规格化的过程中,尾数都要向右移位,这样尾数的低位部分可能会丢失,从而造成一定的误差。为了减少误差,要进行舍入处理,常用的舍入方法有两种:
(1)0舍1入
“0舍1入”,就是指尾数右移时被丢掉的数据的最高位如果是0,那就舍去;如果是1,就在尾数的最低位加上“1”。这种方法,实际上类似于我们平时所说的“四舍五入”。
这种方法的优点是每次舍入产生的误差小,误差控制在2-(n+1)范围内,而且也不会造成误差的累积;但缺点是要进行一次“加1”运算,特殊情况下还有可能造成再次“向右规格化”的现象。
(2)恒置1
“恒置1”,就是指尾数右移时,只要发生低位数据的丢失,尾数的最低位就被设定为1。
这种方法每次舍入所产生的误差比“0舍1入”要大一点,误差控制在2-n 范围内,而且也不会造成误差的累积。其关键特点是舍入处理时无需进行加法运算,所以速度快,也不会造成再次“向右规格化”的现象。
6)溢出处理
尾数相加的溢出不是真正的溢出,可以借由向右规格化作出调整。那么,浮点数的运算会不会产生溢出?什么时候才是真正的溢出呢?
当浮点数在做向左或向右规格化的过程中,阶码也会做相应的调整,也就是说,阶码可能要加上1(向右规格化)或者减去n(向左规格化,n为尾数左移的位数)。显然,这些时候都有可能产生阶码溢出现象。
如果阶码减去n发生阶码溢出,也就是发生阶码的下溢,表示运算结果的精度超出了该浮点数可以表示的范围,也就是运算结果趋近于0。在这种情况下,机器一般认为运算结果就是0;如果阶码加上1发生阶码溢出,也就是发生阶码的上溢,表示数据超出了浮点数可表示的范围,一般认为是+∞或-∞(依赖于尾数的正负)。这种情况才是真正的溢出,机器的溢出标志会被置“1”。
综上所述,浮点数运算真正的溢出,是指在尾数相加的时候发生尾数上溢,并在向右规格化的时候使阶码也发生上溢。
7.浮点乘除法运算
设有两个浮点数x和y,它们的规格化表示分别为:
x=M x×2Ex
y=M y×2Ey
其中Ex和Ey分别为x和y的阶码,Mx、My分别为x和y的尾数。则,两数相乘为:
x×y=M x×2Ex×M y×2Ey=2Ex+Ey×M x×M y 也就是说,两个浮点数相乘的结果就是它们的阶码相加,尾数相乘。
同理,两数相除的结果为:
x/y=M x×2Ex/M y×2Ey=2Ex−Ey×M x/M y
也就是说,两个浮点数相除的结果就是它们的阶码相减,尾数相除。
这样,浮点数的乘除运算就可以转化成定点数的加、减、乘、除运算。
无论是浮点数的乘法还是除法,都可以分为下面4个运算步骤:
1.0操作数检查;
2.阶码加/减操作
3.尾数乘/除操作
4.规格化处理与舍入