一.浮点数定义
浮点数是与定点数相对的概念,计算机中的定点数约定小数点的位置不变,即人为约定俗成地规定了一个数小数点的位置。例如定点纯整数约定了小数点在数值位的最后。定点纯小数约定了数值位的最高位在小数点后面。
由于计算机字长的限制,当需要表示的数据有很大的数值范围时,他们不能直接用定点小数或者定点整数表示。
二.浮点数的形式
其中:
(1)阶码是整数,其位数k+1决定了浮点数表示的数值范围,也就是决定了数据的大小,或小数点在数据中的真实位置。阶符决定阶码的正负。即阶码越长,所能表示的范围越大。
(2)尾数是小数,其位数n+1决定了浮点数的精度,如果尾数采用小数且位数n足够长,则当浮点数运算需要对尾数运算结果舍入时,造成的数据精度损失会比较小。即尾数越长,所能表示的精度越高。
(3)尾数的符号表示浮点数的正负。
1.规格化浮点数
exp不全是0也不全是1,则指数E=e-bias,e为exp的无符号数,bias为2k-1(float为k为8-1,double为11-1),则E的范围为-126127或-10221023。(与有符号数不一样,有符号数是负值多一个,主要原因是-1)。此时小数域frac,认为最高位默认值为1,小数域只存储小数域值(.以后的数)。
上图就是规格化数的表示方式,其中s为符号位,0为整数,1为负数。
同时,E(阶码)等于指数(exp)减去一个常数(偏置数bias) :E=exp-bias;
其中E即转化为二进制的“科学计数法”之后的2的次方数,bias为一个常数,在单精度中为127,在双精度中为1023。
我们来看一个例子:
单精度15213.0转化为规格化编码数。
首先,我们将其转化为二进制数11101101101101,然后符合规格化编码,即M处应当大于2分之1,小于等于1,所以应转化为1.1101101101101乘以2的13次方。之后我们对于尾数frac来讲为23位,而目前1.1101101101101乘以2的13次方的尾数1101101101101只有13位,因此需要补10个0补齐23位,这样尾数frac就写好了。对于符号位s来讲,整数直接写一个0即可。对于指数exp,我们根据上面公式E=exp-bias可知exp=E+bias,这里即为140,然后将140转化为二进制数即可。
不同浮点数的阶码,尾数和偏置数如下:
2.非规格化浮点数
当对尾数M只要求是小数而无其他限制时,此时的浮点数被称为非规格化浮点数。(这句话比较水)
exp全为0时,e为0,但是指数E=1-bias(而非0-bias,为了平滑过渡,通过增加一个1,可以弥补非规格化数frac最高位没有的1),此时frac最高位为0,不为默认的1。好处:1.可以表示0,2.可以表示接近0的数。
3.特殊值(只要是溢出、非数这种情况)
可以发现,
溢出: 当exp=111..11且frac=000..00时,即我们发现到了最大值,实际上就是一个数无穷大导致溢出了,只能用这个最大能表示的表示了。
非数(NaN):这个是exp=111..11且frac不等于000..00时,也就是比我们的最大能表示的还大(逻辑上来看),这种实际上就是表示这不是一个数字,数值无法确定了,所以用这个表示。
4.练习
好了,经过这么多课之后,我们来看一张图,这张图我们规定s符号位为1位,exp为4位,frac为3位。我们给出了相应的s、exp、frac所对应的value(你可以应用上面的知识看一下是否能推出value)。
1.对于Den什么什么也就是非规格化浮点数那一块,我们看此时E=-bias+1,而bias=2的k减一次方减一,这里k为exp的尾数4,也就是E为 -7+1=-6;对照公式,2的E次方为2的-6次方也就是64分之一,同时我们看s位为0,M为个数为为0,而小数点之后为001,即8分之1,所以0 0000 001的大小为-1的零次方乘以M也就是8分之一再乘以2的-6次方分之一也就是64分之一,最终结果为512分之一。
2.对于No规格化那一块,我们找0 0110 110这个东西,和上面步骤一样,我们看s位此时为0,再看这时候exp位不为0,也就是说表示为规格化浮点数,我们知道规格化浮点数为1点几的十进制小数,所以M应该为1+小数点后面的数。对于规格化浮点数,我们知道E=exp-bias,那么这时候bias和上面一样为7,exp这时位0110即6,故E为-1,而小数点后的数位110,也就是2的-1次方加2的-2次方即8分之6,所以M应该为1+8分之6,结果为8分之14,所以最终结果为-1的0次方乘以8分之14乘以2分之一结果为16分之14。
3.最后一行的那无穷大就不说了,记住吧还是。
三.运算
一.五种舍入规则
我们在进行对阶或者右规格化的时候,阶数较小的操作数在进行右移的时候,会造成尾数部分的低位丢失,从而会造成误差。因此我们才需要根据需求,采取四种舍入模式中的一种对尾数进行舍入操作以减少误差。
1.就近舍入:
即十进制下的四舍五入。但是也会出现以下几种情况:
多余数字是1001,它大于0.5,故最低位进1。
多余数字是0111,它小于0.5,则直接舍掉多余数字。
多余数字是1000,正好是等于0.5的特殊情况;那么此时最低位为0则舍掉多余位,最低位为1则进位1。
注意这里说明的数位都是指二进制数。因为这是尾数,所以在计算这些二进制和0.5的关系的时候,也即转为10进制的时候,我们用每一位的权重乘以2^(-i)然后求和即可。
例子:
2.向0舍入 :
即朝数轴零点方向舍入,所以我们直接截尾即可。
对负数来说:
对于-1.001_1000,舍入处理后为-1.001(直接去掉多余的4位)
对于-1.010_1000,舍入处理后为-1.010(直接去掉多余的4位)
3.向上舍入:
对正数而言,多余位全为0则直接截尾,不全为0则向最低有效位进1;负数的话不管多余位是多少直接截尾即可。
//正数多余位全为0直接截尾
对于1.001_0000,舍入处理后为1.001(直接去掉多余的4位)
//负数直接截尾
对于-1.001_1010,舍入处理后为-1.001(直接去掉多余的4位)
4.向下舍入:
对负数而言,多余位全为0则直接截尾,不全为0则向最低有效位进1;正数的话不管多余位是多少直接截尾即可。
//负数多余位全为0直接截尾
对于-1.001_0000,舍入处理后为-1.001(直接去掉多余的4位)
//负数多余位不全为0进位1
对于-1.001_1010,舍入处理后为-1.010(去掉多余的4位,加0.001)
5.向偶数舍入:
有效数字超出规定数位的多余数字是1001,它大于超出规定最低位的一半(即0.5),故最低位进1。如果多余数字是0111,它小于最低位的一半,则舍掉多余数字(截断尾数、截尾)即可。对于多余数字是1000、正好是最低位一半的特殊情况,最低位为0则舍掉多余位,最低位为1则进位1、使得最低位仍为0(偶数)。
注意这里说明的数位都是指二进制数。
例子:要求保留小数点后3位。
对于1.0011001,舍入处理后为1.010(去掉多余的4位,加0.001)
对于1.0010111,舍入处理后为1.001(去掉多余的4位)
对于1.0011000,舍入处理后为1.010(去掉多余的4位,加0.001,使得最低位为0)
对于1.1001001,舍入处理后为1.101(去掉多余的4位,加0.001)
对于1.1000111,舍入处理后为1.100(去掉多余的4位)
对于1.1001000,舍入处理后为1.100(去掉多余的4位,不加,因为最低位已经为0)
对于1.01011,舍入处理后为1.011(去掉多余的2位,加0.001)
对于1.01001,舍入处理后为1.010(去掉多余的2位)
对于1.01010,舍入处理后为1.010(去掉多余的2位,不加)
对于1.01111,舍入处理后为1.100(去掉多余的2位,加0.001)
对于1.01101,舍入处理后为1.011(去掉多余的2位)
对于1.01110,舍入处理后为1.100(去掉多余的2位,加0.001)
这里我们需要说一下,在十进制中的向偶数舍入,我们分为两种情况:
1.当我们要舍入的位数数字为5时,我们向偶数的地方舍入,必如1.5,舍入要么为1要么为2,小数为0.5,我们向偶数舍入为2。
2.当不为5时,我们按四舍五入即可。
二.浮点数加减法
浮点数经常被写成如下的形式:
X = Mx * 2Ex
其中Mx为该浮点数的尾数,一般为绝对值小于1的规格化的二进制小数,机器中多用原码(或补码)形式表示。Ex为该浮点数的阶码,一般为二进制整数,机器中多用移码(或补码)表示,给出的是一个指数的幂,而该指数的底常用2、8或16,我们这里先以2为底作例子进行讨论。
浮点加减法的运算步骤
假定有两个浮点数
X = Mx * 2Ex , Y = My * 2Ey
1. 实现X±Y运算,要用如下五步完成:
(1) 对阶操作,即比较两个浮点数的阶码值的大小.求△E=Ex-Ey。当其不等于零时,首先应使两个数取相同的阶码值。其实现方法是,将原来阶码小的数的尾数右移|△E|位,其阶码值加上|△E|,即每右移一次尾数要使阶码加1,则该浮点数的值不变(但精度变差了)。尾数右移时,对原码形式的尾数,符号位不参加移位,尾数高位补0;对补码形式的尾数,符号位要参加右移并使自己保持不变。为减少误差,可用
另外的线路,保留右移过程中丢掉的一到几位的高位值,供以后舍入操作使用。
(2) 实现尾数的加(减)运算,对两个完成对阶后的浮点数执行求和(差)操作。
(3) 规格化处理,若得到的结果不满足规格化规则,就必须把它变成规格化的数,对双符号位的补码尾数来说,就必须是001××…×或
110××…×的形式。这里的规格化处理规则是:
.当结果尾数的两个符号位的值不同时,表明尾数运算结果溢出。此时应使结果尾数右移一位,并使阶码的值加1,这被称为向右规格化,简称右规。
.当尾数的运算结果不溢出,但最高数值位与符号位同值,表明不满足规格化规则,此时应重复地使尾数左移、阶减减1,直到出现在最高数值位上的值与符号位的值不同为止,这是向左规格化的操作,简称左规。
(4) 舍入操作。在执行对阶或右规操作时,会使尾数低位上的一位或多位的数值被移掉,使数值的精度受到影响,可以把移掉的几个高位的值保存起来供舍入使用。舍入的总的原则是要有舍有入,而且尽量使舍和入的机会均等,以防止误差积累。常用的办法有"0"舍"1"入法,即移掉的最高位为1时 则在尾数末位加1;为0时则舍去移掉的数值。该方案的最大误差为2-(n+1)。这样做可能又使尾数溢出,此时就要再做一次右规。另一种方法 "置1"法,即右移时,丢掉移出的原低位上的值,并把结果的最低位置成1。该方案同样有使结果尾数变大或变小两种可能。即舍入前尾数最低位已为0,使其变1,对正数而言,其值变大,等于最低位入了个1。若尾数最低位已为1,则再对其置1无实际效用,等于舍掉了丢失的尾数低位值。
(5) 判结果的正确性,即检查阶码是否溢出。浮点数的溢出是以其阶码溢出表现出来的。在加减运算真正结束前,要检查是否产生了溢出,若阶码正常,加(减)运算正常结束;若阶码下溢,要置运算结果为浮点形式的机器零,若上溢,则置溢出标志。
三.浮点数乘法
浮点数乘除法的运算规则
运算规则:两个浮点数相乘,乘积的阶码应为相乘两数的阶码之和,乘积的尾数应为相乘两数的尾数之积。两个浮点数相除,商的阶码为被除数的阶码减去除数的阶码,尾数为被除数的尾数除以除数的尾数所得的商,下面用数学公式来描述。
假设有两个浮点数x和y:
x=Sxx r^jx
y=Syx r^jy
那么有
xy=(Sx xSy)x r^ (r^jx+ r^jy)
x/y=(Sx /Sy))x r^ (r^jx+ r^jy)
我们可以看出,浮点数乘除运算不存在两个数的对阶问题,故比浮点数的加减法还要简单。
提醒:在运算过程中,需要考虑规格化和舍入问题。
浮点数乘除法运算步骤
浮点数的乘除运算可归纳为以下4个步骤。
第一步: 0操作数检查。
对于乘法:检测两个尾数中是否一一个为0,若有一个为0,则乘积必为0,不再做其他操作;若两尾数均不为0, 则可进行乘法运算。
对于除法:若被除数x为0,则商为0;若除数y为0,则商为∞,另作处理。若两尾数均不为0,则可进行除法运算。
第二步:阶码加减操作。
在浮点乘除法中,对阶码的运算只有4种,即+1、-1、两阶码求和以及两阶码求差。当然,在运算的过程中,还要检查是否有溢出,因为两个同号的阶码相加或异号的阶码相减可能产生溢出。
第三步:尾数乘/除操作。
对于乘法:第2章讲解了非常多的定点小数乘法算法,两个浮点数的尾数相乘可以随意选取一种定点小数乘法运算来完成。
对于除法:同上。
第四步:结果规格化及舍入处理。
可以直接采用浮点数加减法的规格化和舍入处理方式。主要有以下两种:
1)第一种:无条件地丢掉正常尾数最低位之后的全部数值。这种办法被称为截断处理,其好处是处理简单,缺点是影响结果的精度。
2)第二种:运算过程中保留右移中移出的若干高位的值,最后再按某种规则用这些位上的值进行修正尾数。这种处理方法被称为舍入处理。
当尾数用原码表示时,舍入规则比较简单。最简便的方法是,只要尾数的最低位为1,或移出的几位中有为1的数值,就使最低位的值为1.另一种是0舍1入法,即当丢失的最高位的值为1时,把这个1加到最低数值位上进行修正。
当尾数用补码表示时,所用的舍入规则应该与用原码表示时产生相同的处理效果。具体规则是:
1)当丢失的各位均为0时,不必舍入。
2)当丢失的各位数中的最高位为0,且以下各位不全为0时,或者丢失的最高位为1,
3)当丢失的最高位为1,以下各位不全为0时,执行在尾数最低位加1的修正操作。
我们依旧根据例题来加深我们对其的理解:
【例1】假设, 分别有如下补码(尾数): 1100001.11100000 1110101010111100试对上述4个补码进行只保留小数点后4位有效数字的舍入操作。
解析:对于1.01110000,由于待丢失的后4位全为0,因此应该遵循规则1),当丢失的各位均为0时,不必舍入。因此,舍入后的补码为1.0111。
对于1.01111000,待丢失的后4位为100与规则2)相吻合,即丢失的最高位为1,以下各位均为0时,舍去丢失位上的值。因此,舍入后的补码为1.0111。
对于1.01110101,待丢失的后4位为0101,与规则2)相吻合,即当丢失的各位数中的最高位为0,且以下各位不全为0时,舍去丢失位上的值。因此,舍入后的补码为1.0111
对于1.01111100待丢失的后4位为1100,与规则3)相吻合,即当丢失的最高位为1,以下各位不全为0时,执行在尾数最低位加1的修正操作。因此,舍入后的补码为1.0111+1=1.1000。
四.int和float/double类型转换
一.类型转换
1.double/float类型转化为int类型
1.截断尾数部分
2.向0舍入
2.int转化为double类型,会精确转换,因为int的位宽小于53
3.int转换为float类型,int转为float类型时,不会发生溢出,但是有可能发生舍入。因为一般有float有24位用来表示有效数字,对于整数来说,超过2^24之后,很多数字都没法精确表示了,比如2^24+1。如果把2^24+1这个int转化位float,就只能转换成最接近的2^24。
4.double转为float类型,因为数值范围要小一些,所以值可能发生溢出成为正无穷或者负无穷。另外由于精度较小,可能会发生舍入。
二.代码运行分析
这段代码运行结果是1095237632;
我们来思考一下程序为什么会得出这样的结果,其实和IEEE 754单精度浮点数的存储规则密切相关:
学过c语言指针的同学不难看出,上面的代码不过是将单精度浮点数a 4个字节的存储空间中的所有数以整数int形式读出了而已。
下面我们根据IEEE 754单精度浮点数的规则,来推断12.5在4个字节的存储空间里是以怎样的二进制形式存放的。
IEEE 754单精度浮点数规定,1位数符,8位阶码(移码),23位尾数(原码)。
那么12.5的
转换之后与程序得出的结果一致,这就证明了C语言中float类型确实采用了IEEE 754单精度浮点数的标准。
终于,终于,如果你认真搞懂上面内容,那么浮点数这一块你已经功德圆满了,最后完结撒花!!!
创作不易,记得关注博主哦!