【c语言】浮点数在内存中的存储

引入:整形和浮点型在内存中的存储

浮点数包括以下几个类型:float、double、long double

float类型大小为四个字节,double类型大小为八个字节,相应的,整型类型也有相似大小的类型,int类型大小就为四个字节,long long为八个字节。

值也类似:

类型

int

float

long long

double

3

3.0

4

4.0

整形和浮点数在值的表示上是有交集的,下面我们通过一个实验来对其内存存储形式进行深入探究:

先大胆提出假设“整形和浮点型在内存中的存储形式是不同的”。

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

分别以整形和浮点数将一份内存以不同的类型打印出来(浮点数初始化,和整形初始化情况下),如果打印出的结果不同则说明对于同一份内存,整形和浮点数的读取方式不同,由此又可以推出,对于3(3.0)这个值,存在内存中的格式也必定不相同。

代码运行——>

上半段代码所有语句都是围绕n所在内存的操作。

打印出来的结果可以说是大相径庭,以int形式打印出来是9,可以float的形式打印出来甚至输出了0.000000这么个”古怪“的结果。

代码下半段,通过指针pFloat对n所在的内存空间进行了修改,使值9.0以浮点数的格式存入该内存空间。

这次*pFloat的值正常了,但n的值却又”古怪“了起来,不过通过上下段代码的运行结果可以得出结论,“整形和浮点型在内存中的存储形式是不同的”这个假设是正确的。

主题:浮点型在内存中的存储

对于整形和浮点型在内存中的存储形式究竟有何区别,可以用printf以%p(十六进制)打印出来,直观的对比一下。

十六进制转换为二进制数:

00000000 00000000 00000000 00001001

01000001 00010000 00000000 00000000

整形的二进制存储相信大家都很清楚,以整形存储的逻辑来看浮点型确实有种一团乱麻,摸不着头脑的感觉。

其实浮点型的存储是由国际标准IEEE(电气和电子工程协会) 754规定的,任意一个二进制浮点数V可以表示成下面的形式:

  • (-1)^S * M * 2^E

  • (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。

  • M表示有效数字,大于等于1,小于2。

  • 2^E表示指数位

即:二进制浮点数由S、E、M的三部分组成,二进制表示为“S+E+M”。

双精度:

单精度:

对于flaot型浮点数,S占1个比特位,E占8个比特位,M占23个比特位。

那么我们就可以对n转化的二进制数进行拆分,S‘0’,E‘1000001 0’,M‘0010000 00000000 00000000’,也就是S=0,E=130,M=4096,但如果带入公式“(-1)^S * M * 2^E”,发现结果为2^130*4096,怎么看也和9.0扯不上关系。

显然,简单带入的理解是错误的,重回定义,2^E表示指数位,我们不妨以指数的视角再来看待E。

1.E部分

要完全理解首先要知道什么是指数位,对于一个十进制数10086.0,可以表示为1.0086*10^4,其中4就是指数;对于一个二进制数浮点数1001.1(等十进制数9.5),可以表示为1.0011*2^3,其中指数为3。

接着往下看:

E为一个无符号整数(unsigned int)

这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^1的E是1,所以保存成32位浮点数时,必须保存成1+127=128,即10000000;E是-1时,E=-1+127=126,即01111110;E是0时,E=0+127=127,即01111111。

4

3

2

1

0

-1

-2

-3

-4

10000011

10000010

10000001

10000000

01111111

01111110

01111101

01111100

01111011

取值范围为0~255,即E取值范围+127~-127,E全0全1单独讨论。

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

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

关于E全0全1单独讨论的原因最后再说。

2.M部分

前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。

IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字,对于足够多的二进制位,这样的精度提升无疑是巨大的。

3.S部分

这个部分最简单,在公式“(-1)^S * M * 2^E”中,当S为1,(-1)^1=-1,公式计算出来的结果自然而然的成了负数;当S为0,(-1)^0=1,公式计算出来的结果自然而然的成了正数。

应用:尝试对内存中二进制浮点数数进行转化

知道了SEM三部分的含义,我们来尝试着重新对引入部分的例子进行解析,这里主要讨论浮点数部分,而为了方便这里也再写一遍代码。

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

首先还是上半部分:

”printf("n的值为:%d\n",n);“没什么好说的,重要的是”printf("*pFloat的值为:%f\n",*pFloat);“,%f就是要以浮点数的理解方式来理解n的值。

  1. 已知,n存储的32位二进制为:00000000 00000000 00000000 00001001。

  1. 首先将32位二进制位划分成SEM三部分,S部分”0“,E部分”0000000 0“,M部分”0000000 00000000 00001001“。

  1. 再代入公式,得(-1)^(1-127)*2^0*1.0000000 00000000 00001001,即1*9*9(之所以为9,是因为结果6翻了)

  1. 最后得出一个接近无穷小的浮点数,所以打印结果就该是0.000000。

再说下半部分:

对于直来直去的”printf("*pFloat的值为:%f\n",*pFloat);“不感兴趣,还是讨论重要的”printf("n的值为:%d\n",n);“。

1.首先,计算出浮点数的32位二进制表示。

2.S符号位,9.0为整数,S自然为0;

3.E指数位,9.0转成二进制为1001.0,即1.001,指数位E应该要等与3,实际保存为10000010 (3+127=130);

4.M有效数字(小数部分),1.001去掉前面的1有效数字是001,余下补0,即M部分为0010000 00000000 00000000。

  1. 最后组合起来,9.0 = 01000001 00010000 00000000 00000000。

  1. 将得到的二进制数以整形的理解方式转化为:1091567616。

结果不能说差不多,只能说一模一样。

补充:E全0全1讨论及无限小数

1.E全0全1讨论

在”应用“部分的上半部分代码讨论中就出现了全0的情况,

(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)

可以说是非常小,乃至于无限接近于0,这种情况下将全为零设置为(1-127)可以说和本来结果大差不差了。(我是这么理解的)

对于全1的情况,是对无穷大的讨论,这点和全0的情况类似。

2.无限小数问题

5.5 二进制可以表示为101.1

5.625 二进制可以表示为101.11

那么,5.3呢?

5很容易表示,小数位的3就麻烦了,小数位的第一位表示2^(-1),即0.5;小数位的第二位表示2^(-2),即0.125;小数位的第三位表示2^(-3),即0.0625;小数位的第四位……

经过不断的尝试我们发现,无论加到多少位,始终不能表示5.3,只能无限接近与它。

#include <stdio.h>
int main()
{
    float a = 5.3;
    printf("%p", a);
    return 0;
}

运行结果——>

转化为三十二位二进制数:01000000 00000000 00000000 00000000

调试中&a的内存显示(小端存储)——>

转化为三十二位二进制数:01000000 10101001 10011001 10011010

以%lf打印———>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值