最近学习计算机组成原理,遇到许多疑难问题,接下来写一写我在学习浮点数方面知识的理解,在巩固的同时也方便日后的复习。
一、浮点数的表示
1、浮点数的表示格式
浮点数,顾名思义,就是小数点不固定的数。计算机中,根据小数点位置是否固定,分为两种数据格式,一种就是这个,小数点不固定,另一种是定点数,小数点是固定的。
书上科学地对浮点数表示法的定义是,以适当的形式将比例因子表示在数据中,让小数点的位置根据需要而浮动。我们计算机的容量有限,不可能对每个数都用特别多的位数来表示,比如说2×10^99,这种非常大的数不可能用定点数来表示,所以呢利用浮点数就可以在位数有限的情况下扩大数的表示范围,同时能保持一定的有效精度。
通常情况下,浮点数表示为:N = r^E × M
式子里面r是浮点数阶码的底在计算机中是隐含的,通常情况下r=2。E和M都是带符号的定点数,E叫做阶码,M叫做尾数。其中E的大小越大,能表示的数范围越大,M的位数越大,数的有效精度越高。
简单地举一个例子,1.111×2^100这里面1.111就是尾数,100就是阶码,显然这里阶码占的位数为3位,尾数占的位数是4位,假如阶码占的位数有4位,位数占的位数是3位(阶码和尾数所占位数总和不变),那么这个数就只能表示为1.11×2^0100,显然能表示的数的范围变大了,就这个例子来说原来尾数1.111转变为1.11损失了0.001,这就是精度的损失。
浮点数的一般格式:
这里J是阶符,表示阶码的符号,S是数符,表示浮点数的符号,阶符J和阶码的m位合起来表示浮点数的表示范围和小数点的实际位置,n位尾数反映了浮点数的精度。
2、浮点数的规格化
为什么要进行浮点数的规格化呢,我们知道了浮点数的一般格式,同一个浮点数可以有很多表现形式,比如1.111×2^3,还可以表示为0.1111×2^4,还可以表示为11.11×2^2,那我们总不能任意地表示一个数吧,另外还有一个问题就是,如果尾数位数只有4位,我们想要表示同一个数1111采取两种方法,一个是 0.0111x2^5 和 0.1111×2^4(二者最高为是数符位S),我们可以很清晰地看到,如果采用阶码为5的方法,我们损失了一位精度,阶码为4的方法表示这个数更为精确。
所以,为了提高运算精度,就要充分利用尾数的有效数位,也就是浮点数的规格化,即规定尾数的最高数位必须是一个有效值。非规格化数转变为规格化数,转变过程就是通过调整尾数和阶码的大小,使尾数最高位保证是一个有效值。通常有两种规格化操作:
左规:当浮点数运算结果是非规格化数的时候,要进行规格化操作,将尾数算术左移一位,阶码减1(基数为2时),左规操作可能要进行多次。
右规:当浮点数运算结果尾数出现溢出的时候,将尾数算术右移一位,阶码加1。需要右规操作的时候只需要操作一次。
规格化浮点数的尾数M的绝对值应该满足这样的关系:1/r ≤ |M| ≤ 1(r就是我们的阶码的底,也是基数)。
以r=2为例:
1、原码尾数规格化后:正数为0.1xxxxxx形式,最大值为0.11......1;最小值为0.100......0。负数为1.1xxxxxx形式,最大值表示为1.10......0;最小值表示为1.11......1。
2、补码尾数规格化后:正数同原码正数。负数为1.0xxxxxx形式,最大值表示为1.01......1;最小值表示为1.00......0。
需要注意的是基数,刚刚是以基数为2时的规格化形式,补码规格化数的尾数最高位一定与尾数符号位相反。基数不同的时候,规格化的形式不同,当基数为4时,原码规格化形式的尾数最高两位不全为0;基数为8时,原码规格化形式的尾数最高三位不全为0。
如何判断一个浮点数是否是规格化数:规格化浮点数的尾数小数点后的第一位一定是个非零的数。因此对于原码编码的尾数来说,只要看尾数的第一位是否为1就行;对于补码表示的尾数,只要看符号位和尾数最高位是否相反。(IEEE 754标准的浮点数尾数是用原码编码的)
3、IEEE 754标准
IEEE 754标准采用的浮点数的格式:
ms为数符,表示浮点数的符号,E为阶码部分,用移码表示,M是尾数部分,用原码表示。
IEEE 754标准规定常用的浮点数格式有短浮点数(单精度、float)、长浮点数(双精度、double型)、临时浮点数。
短浮点数数符占1位;阶码占8位,以2为底,用移码表示,阶码偏置值为127(阶码全1表示无限大,E的范围是1~254,空出全0表示非规格化数);尾数部分为23位。
长浮点数数符占1位;阶码占11位,以2为底,用移码表示,阶码偏置值为1023(阶码全1表示无限大,E的范围是1~2046,空出全0表示非规格化数);尾数部分为52位。
在IEEE754标准中长浮点数和短浮点数的尾数采用隐藏位策略的原码表示,什么意思呢。举个例子来说,以短浮点数为例,尾数占23位。我们知道,规格化浮点数后,尾数最高位一定是一位有效值,对于二进制浮点数来说,尾数最高位一定是1,那么我们为了充分利用资源,既然最高位一定是1了,我们干脆就把它隐藏好了,因此尾数实际上是24位,在表示的时候尾数23位是纯小数。比如说十进制12(1100),转化为二进制浮点数规格化后为1.1×2^3,这里我们就把整数部分的1 隐藏了,整数部分的1不存在23位尾数中,存的时候就是这样的32位:0 1000 0010 1000 0000 0000 0000 0000 000。
二、浮点数的加减运算
浮点数运算的特点是阶码运算和尾数运算分开来算。加减运算一律采用补码。具体运算分为以下几步。
1、对阶:对阶的目的是让两个数小数点的位置看齐,使这两个数的阶码相等。显然1.1×2^3和1.1×2^4是不能直接相加减的。原则是小阶向大阶看齐,像这个例子,就是1.1×2^3的尾数右移一位,阶码加一,直到两个数的阶码相等。
2、尾数求和:阶码对齐之后就可以直接按照定点数的加减法则运算尾数了。
3、规格化:尾数求和后的结果如果不是规格化数需要规格化,以双符号位运算为例的话,如果运算结果为正数,规格化的形式应该是00.1xxx......x,如果运算结果为负数,规格化后的形式应该是11.0xxx......x,不符合这种形式的数要进行左规或者右规的操作让其变成这种形式。(在尾数没有溢出的情况下,即尾数结果的双符号为不是10或01的时候,操作都是左规操作,左规操作可能不止进行一次,倘若双符号位为01或10则表明尾数已经溢出了,就要进行右规操作,右规只需要进行一次)
4、舍入:在对阶和右规的操作中,我们都是将尾数右移,阶码加一,由于我们的位数是有限的,在右移的操作过程中很有可能就将低位的尾数丢失了,这会引起误差和精度问题。常用的减小误差的方法有“0”舍“1”入法:即在尾数右移时,被移去的最高数值位为0则舍去,如果被移去的最高数值位为1则在尾数末位加1,如果加1之后又产生溢出则再右规操作一次。恒置“1”法:看名字就可以知道,无论丢掉的最高数值位是1还是0,都使右移后的尾数末位置1。这种方法可能使尾数变大或者变小。
5、溢出判断:既然定点数运算可能溢出,浮点数同样也会溢出,我们已经知道浮点数的表示方法和加减运算规则,既然是溢出,那么肯定是超出了浮点数能表示的范围,浮点数的范围主要是由阶码决定的,如果运算结果规格化后阶码产生了溢出,那才是浮点数的溢出。浮点数的溢出与否是由阶码的符号决定的。以双符号位的补码为例,如果阶码的符号位出现01或10则说明阶码溢出了,01表示阶码大于最大阶码,上溢,进入中断处理;10表示阶码小于最下阶码,下溢,按机器零处理。(溢出时真值的符号位和高位符号位保持一致)还要注意的一点是尾数之和(差)可能会造成尾数的溢出,这并不代表整个的溢出,需要右规一次看阶码是否溢出才能判断。
以上便是我学习浮点数的时候结合书与自己的理解做出的总结,例子都是以基数为2时举的,如果有错误还望看出来的小伙伴勿责怪,帮忙指明一下下。