浮点型数据在内存中的存储(详解)

一、引入

首先,我们看以下C代码:

#include<stdio.h>
int main()
{
    
    
    int a=5;
    float* f=(float*)&a;//取a的地址并将其强制类型转换成浮点型指针
    printf("a的值为:%d\n",a);
    printf("*f的值为:%f\n",*f);
    *f=5.0;
    printf("a的值为:%d\n",a);
    printf("*f的值为:%f\n",*f);
    return 0;
}

如果我们不了解浮点型数据在内存中的存储方式,或许我们会得到如下结果:
a的值为:5
*f的值为:5.000000
a的值为:5
*f的值为:5.000000
而我们将代码进行编译运行时,则会得到如下正确结果:在这里插入图片描述
看似不可理解的值我们是怎样得到的呢?下面正式进行浮点型数据在内存中的存储方式的讲解:
首先,我们要了解如下规定

二、IEEE 754相关规定

(一)表示规定

国际标准IEEE(电子电气工程协会)754规定(查看详情),任意一个浮点数V可以表示成以下形式:
(-1)S * M * 2^E
·(-1)^S 表示符号位,S=0时,V为正数;S=1时,V为负数
·M表示有效数字,取值范围为1<=M<2
·2^E表示指数位

下面举例说明,十进制的5.0,二进制表示为101.0,相当于1.01*2^2,其中S=0,M=1.01,E=2

(二)内存分配

对于32位的浮点数(单精度浮点数),最高的一位表示符号位S,接着的8位表示指数E,剩下的23位为有效数字M
单精度浮点数存储方式
对于64位的浮点数(双精度浮点数),最高的一位表示符号位S,接着11位表示指数E,剩下的52位表示有效数字M双精度浮点数存储方式

(三)存储方式

M第一位始终为1,在计算机内部保存M时,1被舍去,只保留后面的小数部分,等到被读取的时候,再把第一位的1加上去,该存储方式的目的是节省一位有效数字(以32位浮点数为例,留给M的只有23位,将第一位舍去后,相当于可以保存24位有效数字)。*

规定E为一个无符号整数,如果E为8位(单精度),取值范围位0~255,如果E为11位(双精度),取值范围为0~2047。而在实际操作中,E是允许出现负数的,所以IEEE 754规定,存入内存时E的真实值必须加上一个中间数,将其转换为正数,对于8位的E,这个数为127,对于11位的E,这个数为1023。例如,当E=8时,32位浮点数存储为为8+127=135,即10000111,当E=-3时,32位浮点数存储为-3+127=124,即01111100。

为帮助读者深入理解该规定,可通过如下代码进行进一步解释

#include<stdio.h>
int main()
{
    
    
    float f=10.5;
    //10.5的二进制表示形式为1010.1(注意不要写成1010.101,小数点后的1表示2^-1,转换为十进制为0.5)
    //按照IEEE 754规定:S=0,M=1.0101,E=3,存储的计算值为3+127=130
    //在内存中的存储方式应为0 10000010 01010000000000000000000
    //即0100 0001 0010 1000 0000 0000 0000 0000
    //转换为十六进制为0x 41 28 00 00
    return 0;
}

通过调试观察f的内存情况如下
f的内存情况
与上述分析结果一致

(四)数据的取出

当E从内存中取出时,则分为以下三种情况:

1、E不全为0或不全为1

E的计算值减去127得到真实值,再将有效数字前加上第一位的1,实际上为E存储时的逆推过程,此处不做过多赘述。

2、E为全0

假设内存中有如下二进制序列(32位浮点型)
0 00000000 10100010000000000000000
此处E的计算值为0,那么真实值为-127
该处指数为-127,可知该数几乎趋近于0。
当然,以上是我们假设的情况,而在IEEE 754的规定中,此时浮点数的指数E=1-127(或1-1023),有效数字M也不再加上第一位的1,而是还原为0.xxxxx的小数。这样做的目的是为了表示±0,以及趋近于0的很小的数字。

3、E全为1

与E全为0的情况类似。此时表示±无穷大(正负取决于符号位s)

三、代码结果分析

当我们理解以上内容后,我们再回到文章开始时的C代码

#include<stdio.h>
int main()
{
    
    
    int a=5;
    float* f=(float*)&a;
    printf("a的值为:%d\n",a);
    //a的补码 00000000 00000000 00000000 00000101
    printf("*f的值为:%f\n",*f);
    //以浮点型数据类型存储该二进制序列 00000000 00000000 00000000 00000101
    // 0 00000000 00000000000000000000101
    //此时对应E全为0时的情况,该值趋近于0
    //由于单精度浮点型打印时只保留小数点后六位,故*f的打印结果为0.000000
    *f=5.0;
    printf("a的值为:%d\n",a);
    //5.0的二进制表示为101.0,故S=0,E=2,实际存储值为2+127=129,M=1.01
    //故浮点型数据5.0存储的二进制序列为0 10000001 01000000000000000000000
    //对应的十进制表示为1084227584,如下图
    //即a打印的值为1084227584
    printf("*f的值为:%f\n",*f);
    return 0;
}

二进制转换
由此可见,上述代码运行结果是理所当然的。

以上内容均已32位浮点数(单精度)为讨论对象,64位浮点数(双精度)情况与之类似,如果感兴趣可参照上述方法自行讨论。

相信你通过以上学习,一定对浮点型数据的存储方式有了更深的理解。

猜你喜欢

转载自blog.csdn.net/weixin_75094128/article/details/129218636