前言
在上一期中我们讲到了有关于整型在内存中的存储,新朋友可以点开🔗了解一下,那这一期中我们将讲到的浮点数是不是存储方式和整型一致呢?
一、浮点数在内存中的存储
为了探究这个问题我们先来看一段代码
#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;
}
你们觉得运行结果会是什么样的呢?让我们来看一看。
二、浮点数存储规则
我们看到上面代码的输出结果是不是有点意料之外,其实这都和浮点型在内存中的存储规则有关系。
浮点数在计算机中的存储遵循IEEE 754标准,它定义了浮点数的表示和运算规则。根据您提供的图片内容,一个浮点数 VV 可以表示为:
在IEEE 754标准中,浮点数的存储通常分为单精度(32位)和双精度(64位)两种格式。单精度浮点数的存储结构如下:
- 1位符号位 SS
- 8位指数位 EE
- 23位有效数字位 MM
双精度浮点数的存储结构如下:
- 1位符号位 SS
- 11位指数位 EE
- 52位有效数字位 MM
指数位 EE 使用偏移量表示,单精度浮点数的偏移量是127,双精度浮点数的偏移量是1023。这意味着实际存储的指数值需要加上这个偏移量才能得到实际的指数值。
有效数字位 MM 通常使用隐式偏移表示,即在有效数字的最高位之前隐含了一个1(对于非规格化数)或0(对于规格化数)。这意味着在存储时,最高位的1是不存储的,从而可以存储更多的有效数字。
举个例子
假设我们有一个十进制数 3.5,我们想要将其转换为32位单精度浮点数格式。
-
确定符号位:由于3.5是一个正数,符号位 SS 为0。
-
将数值转换为二进制形式:3.5的二进制形式是11.1(无限循环小数,但在有效数字位内,我们只取前几位,例如11.1000...)。
-
规范化数值:将数值规范化,使其形式变为1.xxxxxx * 2^n。对于3.5,我们可以将其表示为1.1 * 2^1。
-
计算指数位 EE:规范化后的数值的指数是1,加上偏移量127(因为这是单精度浮点数),得到 E=1+127=128E=1+127=128。128的二进制形式是10000000。
-
计算有效数字位 MM:去掉规范化数值的整数部分1,剩下的小数部分是1.1000...。我们只取前23位(因为单精度浮点数有23位有效数字位),得到0.1100000...。将这个小数转换为二进制,我们得到1100000...(这里省略了23位之后的位)。
-
组合符号位、指数位和有效数字位:将这三部分组合起来,我们得到32位的二进制数:
- 符号位 SS:0
- 指数位 EE:10000000
- 有效数字位 MM:1100000...
最终,3.5的32位单精度浮点数表示为: 0 10000000 1100000000000000000000
三、浮点数的存储过程
对于有效数字M和指数E有一些特别的规定
当1<=M<2,M可以写成1.xxxxxx的形式,xxxxx表示小数部分。计算机内存保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
E为一个无符号整型:意味着它的取值范围为0~255;但是科学计数法中的E是可能出现负数的,所以存入内存时E的真实值必须再加上一个中间值, 对于8位的E,中间值是127,11位的E,中间值是1023.
3.1浮点数取出过程
3.1.1E不全为0或不全为1
-
读取符号位 SS:这是最左边的一位,决定数值的正负。
-
读取指数位 EE:接下来的几位是指数位,它们表示数值的量级。
-
读取有效数字位 MM:剩余的位是有效数字位,表示数值的精度。
-
计算实际指数:将存储的指数值减去偏移量,得到实际的指数。对于32位单精度浮点数,偏移量是127;对于64位双精度浮点数,偏移量是1023。
-
计算有效数字:将有效数字位 MM 转换为二进制小数,并在前面加上一个隐含的1(对于规格化的数),得到 1.M1.M 的形式。
-
计算数值:将 1.M1.M 乘以 22 的 E−偏移量E−偏移量 次幂,得到最终的十进制数值。
举个例子,假设我们有一个32位单精度浮点数表示为:
1 01111011 00100000000000000000000
-
符号位:最左边的位是1,表示这是一个负数。
-
指数位:接下来的8位是01111011,转换为十进制是123。
-
有效数字位:剩余的23位是00100000000000000000000,转换为二进制小数是0.100000...(这里省略了23位之后的位)。
-
计算实际指数:123 - 127 = -4。
-
计算有效数字:1.100000...(前面隐含的1加上读取到的二进制小数)。
-
计算数值:1.100000...×2−41.100000...×2−4。
将 1.100000...1.100000... 转换为十进制小数,我们得到1.0,然后乘以 2−42−4(即除以16),得到最终的十进制数值0.0625。因此,这个32位单精度浮点数表示的是-0.0625。
3.1.2E全为0
浮点数指数E等于1-127(1-1023)即真实值,有效数字M不再加上第一位的1,而是还是为0.xxxxx的小时。这样是为了表示+-0,以及接近0很小的数字。
3.1.3E全为1
这时,有效数字M全为0,则表示无穷打(正负取决于符号位s)
四、题目解析
为了观看方便我们再把开头的代码和运行结果贴在这里。
#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;
}
为什么这里9还原成浮点数就成了0.000000了呢?下面是9以整型存储再内存中得到的二进制序列。
0000 0000 0000 0000 0000 0000 0000 1001
我们将9的二进制按浮点数的形式拆分:
- 得到符号位S=0
- 后面8为指数E=00000000
- 最后23为有效数字M=000 0000 0000 0000 0000 1001.
按照E全为0的规则浮点数V应该等于:
V是一个很小的接近0的正数,用十进制表示就是0.000000/
再看看浮点数9.0用整数打印为什么是1091567616?
- 浮点数 9.0=二进制的1001.0
- 换成科学计数法是1.001*2^3
- 符号位S=0
- 有效数字M=001(后面加20个0)
- 指数E=3+127=130=10000010
所以写成二进制形式应该是S+E+M:
0 10000010 001 0000 0000 0000 0000 0000
整数在内存是补码转换成原码就是1091567616。
本期内容到此结束啦,如果对您有帮助的话点亮小星星吧。