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

一个小例题

🌤请问下面输出的结果是什么?
得出结果看看和自己预期的是否一样。

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;
}

一个有点意思的事情是,浮点数,明明就是小数,为啥大家都叫小数,因为小数有个点符号,可以左右浮动写成科学计数法方式咯。
最终结果:
在这里插入图片描述
为什么结果是这样呢?这是与浮点数在内存中存是有关系的。


浮点数在内存的存储规则

浮点数也是以二进制形式存入内存,只不过不像整形存储的方式那么简单。

浮点数的分解形式规定

根据国际标准 IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)S × M ×2E

  • (-1)s表示符号位,当s=0,V为正数;当s=1,V为负数。
  • M表示有效数字,大于等于1,小于2。
  • 2E表示指数位。

举几个例子:

比如 5.5为浮点数
写成二进制的模样为:101.1;
转化为科学计数法:1.011*2^2;(^这个表示取幂的意思,不是异或的意思,2^2表示22次方)
由于5.5是正数,所以符号位为(-1)^0;
写成国际标准形式:(-1)^0 * 1.011*2^2;
其中 S = 0;
M = 1.011;
E = 2;
这个时候你就发现,一个浮点数,被拆解成为了3部分,这是IEEE754协会所规定好的。

再比如 浮点数 -5.0,这是负数

写成二进制: -101.0;
转化为科学计数法:-1.010*2^2;
由于-5.0为负数,所以符号位为(-1)^1;
写成国际标准形式:(-1)^1*1.01*2^2;

其中:S = 1;
M = 1.01;
E = 2;

  • 有木有发现 正数 S = 0;负数 S = 1;这是和整数区分正负数的符号位统一一样的;
  • 还有 M 永远是大于等于1以上的数,且,小于2,因为这就是二进制数呀;
  • E就是指数位了。

这就是IEEE746协会规定好的东西。


浮点数在内存表示形式

IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
在这里插入图片描述
对于64位的浮点数,最高的1位是符号位s,接着的11位是指数E,剩下的52位为有效数字M。
在这里插入图片描述


那么是如何具体的把 S E M 存入到内存中的呢?
对于S
很简单,判断是正数,就在最高位存 0;是负数 ,在最高位存 1;

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

比如:5.5;
国际标准形式为:(-1)^0*1.011*2^2;
其中 S = 0;
M = 1.011;
E = 2;
在内存中 M的位置就存 1.001的小数点后面的 001,把1舍弃,等到读取时候再取出来;

那么,在内存中(假如是32位)
在M位置上填 011,后面不够的位置补上0就可以。
在这里插入图片描述


至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int)
这意味着,如果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。

举个例子
浮点数 0.5;
转化位二进制 0.1;
转化为国际标准形式为:(-1)^0 * 1.0*2^(-1);
其中
S= 0;
M = 1.0;
E = -1;
这里E就表示了负数了,要存入计算机中:-1+127 = 126;
转化为二进制:0111 1110,在内存E的位置就是这么存放的

那么存入的时候,在读取的时候就会又三种情况:

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

  1. E不全为0或不全为1
    这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

  2. E全为0
    这时,说明你指数值是-127,或-1023,即2-127,或2-1023,这个数字表现为超级小,所以还原真实值,读取时候,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

  3. E全为1
    这时,说明你的指数值为128,(因为无符号 8 位最大为255),这个是2128,超级大的数字,则,我们也认为,有效数字M不再加上第一位的1,再加上符号位S,就表正负无穷大的数字了。


回到第一个题的解释

所以对于第一个例子的答案解释有:

int main()
{
int n = 9;
9是整形:二进制补码序列为:
00000000 00000000 00000000 00001001
printf("n的值为:%d\n",n); 结果为9

float *pFloat = (float *)&n;
用浮点型指向整形n,由于floatint都是4个字节所以不会发生越界访问,
并且,存入变量n是9的补码形式,但是对于float类型指针,解释这个补码是
以浮点类型存储方式解释的;
所以对于00000000 00000000 00000000 00001001,指针*pFloat解释为:
   S = 0
   E =  0000000 0
   M =           0000000 00000000 00001001
读取的时候,E由于全0,并且符号位为0,%f是以精度为6位打印:
所以最终结果为*pFloat = 0.000000
printf("*pFloat的值为:%f\n",*pFloat);所以最终结果为*pFloat = 0.000000


*pFloat = 9.0;
9.0是浮点数,转化为二进制:1001.0
国际标准形式为:(-1)^0 * 1.001*2^3;
其中:
S = 0;
E = 001;
M = 3;
所以 二进制序列为:即在内存中存储的是这个数字:


printf("num的值为:%d\n",n);0 00100000 00000000000000000000000%d解释:为1091567616
printf("*pFloat的值为:%f\n",*pFloat); 以浮点数的形式解释:9.000000
return 0;
}

到这里浮点数的一些存储问题也解决了,其实不是很难,就是IEEEE协会规定的一些东西,掌握理解就可。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呋喃吖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值