浮点数在计算机中是如何表⽰的
前⾔
相⽐int等整型,float等浮点类型的表⽰和存储较为复杂,但它⼜是⼀个⽆法回避的话题,那么就有必要对浮点⼀探究竟了。在计算机中,⼀般⽤IEEE浮点近似表⽰任意⼀个实数,那么它实际上⼜是如何表⽰的呢?
下⾯的表达式⾥,i的值是多少,为什么?如果你不确定答案,那么你应该好好看看本⽂。
float f = 8.25f;
int i = *(int*)&f;
IEEE浮点表⽰
IEEE浮点标准⽤
的形式近似表⽰⼀个数。并且将浮点数的位表⽰划分为三个字段:
符号(sign)s决定这个数是负数(s=1)还是正数(s=0)。可以⽤⼀个单独的符号s直接编码符号s。
尾数(signficand)M是⼀个⼆进制⼩数,它的范围是1~2-ξ或者是0~1-ξ。
n位⼩数字段编码尾数M。
阶码(exponent)E的作⽤是对浮点数加权,这个权重是2的E次幂(可能是负数)。k位的阶码字段编码阶码E。
在单精度浮点格式(c语⾔的float)中,s,exp和frac字段分别为1位,8位和23位,⽽双精度浮点格式(c语⾔中的double)中,s,exp和frac 字段分别为1位,11位和52位。
⼀个浮点数的常见⽐特位表⽰如下:
单精度
双精度
⽽根据exp的值,被编码的值可以分为三⼤类不同的情况。下⾯进⾏⼀⼀解释。
情况1:规格化的值
即最普遍的情况,当exp,即阶码域既不为全0,也不为全1的情况。在这种情况下,阶码字段解释为以
偏置(biased)形式表⽰有符号整数,即E=exp-Bias,exp是⽆符号数(1~254)。Bias是⼀个等于的偏置值,对于单精度来说,k=23,Bias=127,因此E的范围是-
126~+127。
frac被描述为⼩数值,且0≤frac<1,其⼆进制表⽰为0.frac。尾数定义为 M=1+frac ,则M=1.frac。那么就有1≤M<2,由于总是能够调整阶码E,使得M在范围1≤M<2,所以不需要显⽰的表⽰它,这样还能获得⼀个额外的精度位。也就是说,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的frac部分,等到读取的时候,再把第⼀位的1加上去。
情况2:⾮规格化的值
当exp,即阶码域为全0时,所表⽰的数便为⾮规格化的值,该情况下的阶码值E=1-Bias(注:为从⾮格式化值转换到格式化值提供了⼀种⽅法)。尾数M=frac
⾮规格化的数有两个作⽤。
表⽰数值0。格式化数中,我们总使得M≥1,因此就⽆法表⽰0。⽽阶码全0时,且尾数也全0时,就可以表⽰0了。
表⽰接近0.0的数。它所表⽰的值分布地接近于0.0,该属性成为逐渐溢出。
情况3:特殊值
有两种
阶码全为1,⼩数域全为0。它得到值为 +∞(s=0)或-∞(s=1),它在计算机中可以表⽰溢出的结果,例如两个⾮常⼤的数相乘。
阶码全为1,⼩数域不全为0。它得到值为NaN(Note a Number)。它在计算机中可以表⽰⾮法的数,例如计算根号-1时的值。
浮点数的范围和有效位
对于浮点数,其能表⽰的数值范围和其有效位如下
类型⽐特位数值范围有效位
float32-3.410^38~+3.410^386~7位
double64-1.710^-308~1.710^30815~16位
long double128-1.210^-4932~1.210^493218~19位
类型⽐特位数值范围有效位
可见同⽐特位数的整型(例如int)要⽐浮点数(例如float)能表⽰的数值范围要⼩很多,但是需要注意的,虽然浮点数能表⽰的范围⼤,但是它却不能精确表⽰在其范围内的所有实数,也就是说,它只能保证有效位的值是精确的,当表⽰的数值(⼩数部分)超过有效位时,所表⽰的数是⽆法保证精确的,甚⾄可以说是错误的。
那么浮点数的数值范围和有效位是如何得到的呢?
浮点数的数值范围计算
有了前⾯了基础,我们就可以来计算浮点数的数值范围了。以单精度(float)为例,我们知道它的指数范围(即E)为-126~+127,⽽M的范围为1≤M<2,实际上,对于单精度,1≤M≤2-2^(-23)(注:23为frac字段所占的⽐特位)。那么我们就可以得到单精度的最⼤值为:
同理,我们可以得到单精度的最⼩值为:
我们仅仅以单精度为例,⽤同样的⽅法可以计算其他精度的浮点数数值范围,在此不再赘述。
浮点数的有效位
float数值范围
有效位也可以理解为我们常说的精度。浮点数的精度是由尾数的位数来决定的。
对于单精度(float),它的尾数为23位,⽽2^23=8388608,共7位,也就是说最多能有7位有效数字,但⾄少能保证6位,因此其有效位为6~7位。当然我们可以通过下⾯的内容进⼀步理解。以下计算结果保留10位⼩数。
观察a和b的结果可以发现,0.0000001和0.0000002之间的其他数是没有办法通过单精度浮点数来精确表⽰的,也就是说,只有到⼩数点后⾯7位的值才是精确的,同理,观察b和c的结果,0.0000002到0.0000004之间的其他数也是不能通过单精度浮点数精确表⽰的,更不幸地是,这之间的数,甚⾄只能精确到第6位。
这也就有了单精度浮点数的有效位为6~7位的结论。根据相似的⽅法,我们同样可以得到双精度浮点数的有效位为15~16位的结论,这⾥不再赘述。
浮点数在内存中的存储
了解了这么多,我们来看⼀下⼀个⼩数究竟是如何在内存中存储的。以float f = 8.5f为例。其⼆进制表⽰为,可见指数实际值为3,则根据
E=exp-Bias,可知exp=E+Bias=3+127=130,根据M=1+frac,可知,frac=M-1=0.0001(⼆进制)⽽
因此不难得到,8.5的在内存中的存储情况为:
s exp frac
01000 00100001 0000 0000 0000 0000 000
如果这个时候把这个值作为整型使⽤,是多少呢?没错,是1091043328
#include<stdio.h>
int main(int argc,char *argv[])
{
float f=8.5f;
int *i = (int*)&f;
printf('%d
',*i);
return 0;
}
再说⼏句
关于浮点数,需要再说⼏句:
在⼆进制,第⼀个有效数字必定是“1”,因此这个“1”并不会存储。
浮点数不能精确表⽰其范围内的所有数。
可精确表⽰的数不是均匀分布的,越靠近0越稠密。
默认舍⼊⽅式为向偶舍⼊,也被称为最接近的值舍⼊。
不遵守普遍的算术属性,⽐如结合律。