1. 前言🚩
我们在前一章数据的存储中介绍了除了浮点数类型以外其他类型在内存中的存储,本章将给大家分享浮点数在内存中的存储的详解,希望我的文章能帮到大家.
2. 浮点数存储与整型存储的关系🚩
如果想直接查看浮点数在内存中的存储比较难,这里我们举一个例子代码来看看
int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
大家可以猜想一下这段代码会打印什么?我们很容易判断出第一个打印以整型形式打印整型应该就是打印9,我们的第四个打印同理,以浮点型的形式打印浮点数应该是打印9.000000
但是我们发现中间两个打印很不符合逻辑,一个打印的是0,还有一个打印了很大的数,也可能是打印的随机值,到这里我们可能已经知道了浮点数在内存中的存储规则和整型是完全不一致的,整型的存储补码在内存中,并且分为有符号位整型和无符号位整型,那么浮点数是怎样存储的?
3. 浮点数存储规则🚩
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
这个规则是什么意思呢?我们举个例子,比如这里有一个浮点数为 5.5.我们把这个十进制的浮点数先转换为二进制的浮点数,为 101.1 1.然后二进制的101.1可以写成1.011×22 2.转换为这种形式后,我们就一一对应上面的S,M,E.
S=0(因为这个数大于0)
M=1.011
E=2
划重点! 相当于所有的浮点数都可以用S,M,E这三个数表示出来,所以我们的内存中只需要存储这三个数即可代表浮点数:
然而对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
这里我们就用32位机器来做讲解,我们知道float类型占四个字节.也就是32个bit位(也就是占32个二进制位),S只占一个bit位,因为S要么是1要么是0,它只用来表示浮点数的正负.E占八个bit位,它最大能存储的是255.M的话是占23个bit位.
4. IEEE 754特殊规则🚩
IEEE 754对有效数字M和指数E,还有一些特别规定:
前面说过, 1≤ M <2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。
这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0 ~ 255;如果E为11位,它的取值范围为0 ~ 2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
懂了这些规则,我们前面距离的5.5浮点数在内存中的存储就应该是这样的:
5. 从内存中取出E的情况🚩
指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.02(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:
0 01111110 00000000000000000000000
1
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
6. 对前面代码的解释🚩
我们把前面的代码和截图拿过来:
int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
我们首先来看第二个打印信息,n现在是以整数的形式存储在内存中,它在内存中的补码是
00000000 00000000 00000000 00001001
但是我们打印信息时,是要将它以浮点数的形式打印出来,所以我们要以浮点数的形式翻译这一段二进制代码,我将它分为浮点数的存储形式方便观察:
0 00000000 00000000000000000001001
我们可以发现,存储E的八个bit位全为0,当E全为0时,相当于它的值等于 (-1)S * M * 2-126,这个值是很小的,float类型的小数点后面六位都是发现不了有效数的,所以这里打印0.000000
接下来再来看第三个打印信息,这里我们把指针解引用后将浮点数9.000000存入内存,这里因为存储的是浮点数,所以在内存中以浮点数的存储规则来生成这32个bit位:(-1)0 * 1.001 * 23
0 10000010 00100000000000000000000
其中,存储E的八个bit位是3+127=130的二进制形式.当我们打印信息时,是按%d打印,也就是按照整型的形式打印,所以编译器在取出数据时是以取出整型的方式来翻译的,我把它写成规范式
01000001 00010000 00000000 00000000
我们再在程序员计算器算出这段二进制代码的十进制形式:
和最后的结果保持了一致,我们的分析是正确的!
7. 总结🚩
浮点数在内存中的存储与整型的存储是有差异的,这一节的内容不需要大家全部背下来,你只要大致知道浮点数在内存中的存储是怎么回事就行了.看见类似于上面第一步的代码你也要能解释出来
🔎 C语言高阶 🔍
这里5.5转为二进制为什么是101.1?首先整数位的5转换为101是没有问题的,而0.5实际上是这样来的:二进制的0.1实际上是写成了1×2-1=0.5.就像101转换为10进制是写做1×22+0×20+1×20=5.这里的小数也按照这个规则来操作 ↩︎
我们知道十进制的1011可以转换成1.011×103,而我们这里的101.1是二进制数,转换成1.011×22,底数2代表这里是二进制数,指数2代表小数点向左移动了两位 ↩︎