C语言浮点数在内存中的存储

一、浮点数在内存中的存储

常见的浮点数:3.14159、1E10等,浮点数家族包括:float、double、long double 类型。浮点数表示的范围:在float.h中有定义。(上面的1E10是科学计数法的意思,1E10==1.0*1010)

1. 练习

看下面一段代码:

#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

程序运行结果:
在这里插入图片描述上面的代码中,n 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大? 通过打印的结果就可以知道,当以%f的形式打印一个整数或以%d的形式打印一个浮点数时,打印的结果不是预期的样子。说明:整数和浮点数在内存中的存储方式是不一样的。

要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
🍧V =(-1)S * M * 2E
🍧(-1)S 表示符号位。当S=0,V为正数;当S=1,V为负数
🍧M 表示有效数字。M是大于等于1,小于2的
🍧2E 表示指数位

举例来说:
十进制的5.5,写成二进制是101.1,相当于 1.011*22。那么按照上面V的格式,可以得出S=0,M=1.011,E=2。十进制的-5.5,写成二进制是-101.1,相当于 -1.011*22。那么,S=1,M=1.011,E=2。

在这里插入图片描述

由上图可知,浮点数的二进制表示中:小数点后面的每个bit位都是有权重的,从左往右依次是2-1,2-2,…。这也反应出一个浮点数可能不能精确的进行存储。上面的5.5能用二进制准确地表示出来是因为小数点后面的0.5正好是一个权重的值,所以能精确表示出来,但要换了其他的不特殊的一些浮点数,可能就无法精确的进行存储。(存储浮点数,其实就是存储与S、M、E相关的值)

IEEE 754规定:
🍑对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

在这里插入图片描述🍑对于64位的浮点数(double),最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
在这里插入图片描述现在知道为什么float叫单精度浮点型,double叫双精度浮点型,明显double存储的M的位数要比float的要多。

二、浮点数存的过程

IEEE 754 对有效数字M和指数E,还有一些特别规定。

1. M的存储

🥥🥥采用科学计数法时,1≤M<2(M是二进制数),也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.011的时候,只保存011,等到读取的时候,再把第一位的1加上去。这样做的目的,是为了节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

2. E的存储

首先,E为一个无符号整数(unsigned int),也就是存储E的最高位是数值位,不是符号位。
这意味着,如果E为8位,它的取值范围为0-255,如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,210的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

三、浮点数取的过程

指数E从内存中取出还可以再分成三种情况:

E不全为0或不全为1时:
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再在有效数字M的前面加上第一位的1。
比如:0.5的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2-1,其阶码为-1+127(中间值)=126,表示为01111110,而1.0去掉整数部分为0,补齐0到23位:00000000000000000000000,则其二进制表示形式为:

0 01111110 00000000000000000000000

E为全0时:
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M前不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

0 00000000 00100000000000000000000

E全为1时:
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

0 11111111 00010000000000000000000

那回到一开始的练习:

#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

我们来解释一下为什么打印出来的值为0.000000、1091567616?
第二个打印的结果为0.000000,为什么呢?本来9是一个整型数字,那在内存中存储的补码形式为:

0000 0000 0000 0000 0000 0000 0000 1001

那站在pFloat的角度,他会以为自己指向的是一个float类型的数值。那将9的二进制序列按照浮点数的形式拆分,得到的第一位是符号位s=0,后面的8位是指数E=00000000 ,最后的23位是有效数字M=0000000 00000000 00001001。由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:

V=(-1)0×0.00000000000000000001001×2-126=1.001×2-126

显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000(保留6位小数)。

再看第三个打印的结果为1091567616,我们通过指针pFloat修改了其所指向空间的值,以浮点数的视角存储了9.0。首先,浮点数9.0等于二进制的1001.0,即换算成科学计数法是:1.001*23,所以:9.0=(-1)0 * (1.001) * 23。那么,第一位的符号位S=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130即10000010

0 10000010 00100000000000000000000

这个32位的二进制数,当以%d(有符号整数)的形式进行打印时,会被解析成一个有符号整数,由最高位是0,则是一个正整数,而正整数在内存中的原反补码相同,所以上面的二进制数转换成十进制数就是1091567616。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米饭「」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值