浮点型如何在内存中进行存储呢?

首先,我们先来了解一下,常见的浮点型的表示形式如下所示:

1、3.1415926           —— 浮点数(字面浮点型);

2、1E10=1.0x10^10 —— 浮点数(科学计数法的浮点型);

为了方便讲解,在这里幂次方都使用 ^ 来表示,,不考虑 ^ 在C语言中是按位异或的情况;

浮点数家族包括:float、double、long double类型,其中float类型所占内存空间的大小为:4bytedouble和long double类型所占内存空间的大小为:8byte

浮点型能够表示的范围,以及类型相关的属性,精度,范围等等,都会在#include<float.h>中有定义,右击头文件转到文档即可查看;

 整型家族的取值范围,以及类型相关的属性都在#include<limits.h>中有定义,右击头文件转到文档即可查看;

当以float(%f)或者是double(%lf)打印浮点型的数字的时候,都会默认在屏幕上打印出小数点后六位,即使小数点后面一位也没有,它也会自动补充到后六位,但是如果在%f或者是%lf

中的f或lf前,%后面加上 点+数字 ,,,就会控制浮点数在屏幕上打印的小数点后的个数,如果不加的话,%f 和 %lf 都会默认打印出小数点后6位,比如:

再如:

 接下来,我们通过一个例子来阐述,整型和浮点型在内存中到底是如何进行存储的?

int main()
{
 int n = 9;
 float *pFloat = (float *)&n;
 printf("n的值为:%dn",n);
 printf("*pFloat的值为:%fn",*pFloat);
 *pFloat = 9.0;
 printf("num的值为:%dn",n);
 printf("*pFloat的值为:%fn",*pFloat);
 return 0; }

首先,观察上述代码,应该注意几个地方:

float *pFloat = (float *)&n;

我们取出整型变量n的地址,强制类型转化为float*的类型,这里只是把类型转化了,该地址的值是没有发生变化的,然后再存到float*类型的pFloat指针变量中,我们知道,地址或指针所占

内存空间的大小,只和平台有关,如果平台是32位的,那么,所占内存空间大小就应该是4byte,如果是64位平台,所占内存空间的大小应该是8byte,和地址或指针的类型是没有关系

,,也就是说,我们是可以把一个整型地址放在一个float类型的指针变量里面去的,一般,我们常使用的平台都是32位平台,他们都占4byte,,所以,是能够放得进去的,在本句代码

里,即使不强制类型转换为float*类型, 也是可以的,,只不过是,类型不匹配,&n的类型应该是int*,放在float*类型中会报警告,但是,是有能力存进去的,运行结果也是正确的,为了

消除警告,我们常常先把类型统一,然后再存放进去;

如果不清楚浮点型在内存中的存储方式的话,我们一般会认为,打印出来的结果分别应该是: 9    9.000000   9    9.000000 ,那么,最终的结果会是什么样呢?

 我们发现,代码跑出来的答案和我们自己想的有很大的差距:

我们观察第一个结果,,n以整型的形式放进去,然后再以整型%d的形式取出来,结果是9,这是合理的,,第二个结果是,n以整型的形式存进去,然后以%f,浮点型的形式取出来,,

int整型占4byte,float类型也占4byte,float*类型的指针变量pFloat,解引用就会访问4byte,跳过一个float类型,而拿出来的结果是不一样的,不是9.000000,这就说明,浮点型和整型在

内存中的存储方式是不一样的,如果一样的话,拿出来的结果应该是一样的,对于第三个结果,,以浮点型的形式存进去,再以%d整型的形式取出来,结果不是9,这又说明了,浮点型和

整型在内存中的存储方式是不一样的,对于第四个结果,以浮点型的形式存进去,然后以%f浮点型的形式取出来,结果是9.000000,这是合理的;

整形的视角放进去,再以整型的视角拿出来,,结果是对的,,以浮点型的视角放进去,再以浮点型的视角拿出来,,结果也是对的,,但是混淆放拿结果就是乱的;



*pFloat
在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大? 要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。

我们知道,对于整型来说,整形在内存中是以二进制补码的形式存放的,并且以十六进制进行展示的,那么我们的浮点型在内存中到底是怎么进行存放的呢?

根据国际标准
IEEE
(电气和电子工程协会)
754

任意一个 
二进制浮点数
V
可以表示成下面的形式:
(-1)^S * M * 2^E
1、(-1)^s
表示符号位,当
s=0

V
为正数;当
s=1

V
为负数,这就和我们的符号位上,0代表正数,1代表负数对应起来了;
2、M
表示有效数字,
大于等于1,小于2
,因为对于二进制数来说,不可能>=2,所以,应该是小于2的;
3、2^E
表示
指数
位。
如果任意一个二进制浮点数都可以写成(-1)^S * M * 2^E这个形式,,那么我只需要把S E M 这三个数存储起来即可,如果知道这三个数,我们完全可以倒推回去,从而先得到二进制浮点型
然后再转化成十进制浮点数,这样就可以还原出来我的真实的十进制浮点数了;
举例来说:
为了方便讲解,在这里幂次方都使用 ^ 来表示,,不考虑 ^ 在C语言中是按位异或的情况;
十进制的
5.0
,写成二进制是
101.0
,相当于
1.01×2^2 ,即,
(-1)^0
*
1.01
*
2^2   
,对于十进制浮点型数字来说,如果是123.123,就可以写成,1.23123 *
10^2
,,所以,类比过来,
对于二进制的浮点型数字,101.0,就可以写成:
1.01*
2^2

那么,按照上面V的格式,可以得出
s=0

M=1.01

E=2
十进制的
-5.0
,写成二进制是
-
101.0
,相当于
-1.01×2^2 ,即:
(-1)^1
 *
1.01
*
2^2    
那么,s=1

M=1.01

E=2

如何将十进制的浮点型数字转化为二进制浮点型数字呢?

法一:

小数点前和小数点后分别进行转化

小数点前面的进制转换就不再介绍,,,小数点后面的就是:

小数部分乘以2取整,在得到的数的小数部分乘以2再取整,在得到的数的小数部分乘以2再取整,,直到小数部分为0.0结束,然后正序进行排列;

比如:将10进制的0.875转化为2进制的小数:

0.875的小数部分是0.875,

0.875×2=1.75 取整得:1 剩下的小数部分为0.75;

0.75x2=1.5 取整得:    1 剩下的小数部分为0.5;

0.5x2=1.0 取整得:      1 剩下的小数部分为 0.0 ;

小数部分为0.0,到此结束,,正序排列为111,即结果为0.111;

将10进制的65.875转化为2进制:

点前后分别求,,先把点拿出来,,最后直接填数就行 ( A ).( B )

整数部分转换后的二进制记为A,小数部分转换后的二进制记为B;

整数部分: 65.,,,,二进制位1000001 ,,所以A =1000001;

小数部分:0.875

0.875×2=1.75 取整得:1 剩下的小数部分为0.75;

0.75x2=1.5 取整得:    1 剩下的小数部分为0.5;

0.5x2=1.0 取整得:      1 剩下的小数部分为 0.0 ;

小数部分为0.0,到此结束,,正序排列为111,,,这就是10进制小数转为2进制小数的结果,,所以B=111

所以: ( A ).( B ) = (1000001).(111) = 1000001.111

如果,十进制浮点型数字小数点后已经是0.0的话,,那么对应的二进制浮点型数字小数点后直接写 .0 或者二进制浮点型小数点后面不写;

例如: 十进制浮点型数字  9.0  转为  二进制浮点型数字得: 1001或者1001.0   ,,我们一般习惯于写上点以及小数点后面的0,,即,一般常用:1001.0

法二:

二进制的浮点数:

所以说,十进制的浮点数5.5,,小数点后面的0.5,可以直接用二进制浮点数中的 .1来表示,同理,十进制的浮点数10.875,,小数点后面的0.875,可以直接使用二进制浮点数中的 .111

来表示;

通过上面的内容,我们就可以把任意一个二进制浮点数对应的,S M  E 写出来,那么这三个数是怎么进行存储的呢?

我们一直,浮点型家族有:float、double、long double,其中float类型所占内存空间的大小为:4bytedouble和long double类型所占内存空间的大小为:8byte,所以,这三者所占内存空

间的大小,分别为,4byte,8byte,8byte,=== 32bit,64bit,64bit;

下图是对于32位的浮点型来说的,即对于float类型来说:

 下图是对于64位的浮点型来说的,即对于double或者是long double类型来说:

 通过上图,我们就知道,浮点型家族的三个类型对应的S M E 的存储位置是怎么样的了,但是,具体怎么存进去的呢,我们下面来继续探讨:

符号位S来说,只有两种可能,要么是0,要么是1,我们继续来讨论M是如何存储进去的:

对于浮点型数来说,他们没有原码,反码,补码的概念,这个概念只是针对于整型来说的,,整型在内存中是以二进制补码的形式存储的,以十六进制进行展示的,整型在内存中是以补码

的形式存储的,,而我们的浮点型数在内存中是按照上述方法进行存储的,我们根据 IEEE754 标准写出来的二进制序列,就是浮点型在内存中的存储方式;

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

前面说过,
1≤M<2

,也就是说,
M
可以写成
1.xxxxxx
的形式,其中
xxxxxx
表示小数部分,IEEE 754规定,在计算机内部保存M
时,默认这个数的第一位总是
1
,因此可以被舍去,只保存
后面的 xxxxxx部分。比如保存
1.01
的时 候,只保存01
,等到读取的时候,再把第一位的
1
加上去,这样做的目的,是
节省1位有效数字,使得精度更高了

32
位浮点数为例,留给M
只有
23
位, 将第一位的1
舍去以后,等于可以保存
24
位有效数字,存储M的时候,
不要将小数点存进去
因为M不可能>=2,所以,小数点前面的数一定是1,,,此时的
不算入位中,等到拿出来的时候,在M前面加个  
1 .  即可,M只把小数点后面的数存进去,
不够的在右侧补0,比如,M
有23bit,我的M取小数点后面为011,则补0为:01100000000000000000000;
 
至于指数E,情况就比较复杂:
 
首先,E
为一个
无符号整数

unsigned int) 这意味着,
如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047
。但是,我们知道,科学计数法中的E是可以出现负
数的,所以IEEE 754规定,存入内存时
E
的真实值必须再加上一个中间数,对于
8
位的
E,这个中间数是
127
;对于
11
位的
E,这个中间数是
1023
。比如,
2^10

E

10
,所以保存成
32
浮点数时,必须保存成
10+127=137,即 10001001。
比如:
如果十进制浮点数  0.5 ,,写成二进制浮点数就是:0.1 ,再写成科学计数法的话应该是: (-1)^
0 *
1.0 * 2^(
-1),,, 此时,指数E就是 -1,,如果按照上述方法来看的话,,E是一个无符
号整型,对于一个福无符号整型来说的话,,是不可能出现一个负数的,这就出现了
矛盾,,所以,标准又规定,存入
内存时
E
的真实值必须再加上一个中间数,对于
8
位的
E,这个中间数

127
;对于
11
位的
E,这个中间数是
1023
所以,如果是十进制浮点型 0.5的话,转为二进制浮点型就是: 0.1,此时S=0,,M=1.0,,E= -1,,如果这是float类型的浮点数,就把 E= -1加上127,得到126,把126存到二进制序列
上E里面去,如果这是double类型的浮点数,就把E= -1加上1023,得到的是1022,把1022存到二进制序列上对应的E里面去,所以在二进制序列上,存的并不是真实的E的值,而是把E的
值修正之后再存进去的。
 
 
目前来看,只是把S M 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的很小的数字。
如果E为全0,出来的结果就是0,这个0是加了127或者是加了1023之后得到的0,也就是说,原来的E的真实值应该是:
-127或者是-1023
,,所以说,还原到二进制浮点型应该是:
± 1.xxxxx * 10^(-127) 或者是: ± 1.xxxxx * 10^(-1023)  这将是一个非常

的数字,,所以我们
干脆也直接不写成 1.xxx,,而是直接写成: 0.xxxx,写成那种都是一个非常小的数

,,这表示
正负无穷接近0
的一个
数,,非常小,我们就按照这个放法来写 E 和 M 
 了,,就不再按照
E为既有0又有1的情况来写E和M了;

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

E为全1的话,得到的E应该是:255或者2047,,这是加上127或者1023得到的值,,所以,E的真实值应该是:128或者1024,,所以,还原到二进制浮点型应该是:

± 1.xxxxx * 10^(128) 或者是: ± 1.xxxxx * 10^(1024)  这将是一个非常

的数字,正负无穷,
在这里我们就不考虑它的E和M的取值问题了;
到此为止,我们已经研究完了浮点型在内存中是如何存储以及如何取出的,接下来我们再返回去看一下我们的例题:

首先,n=9,,9的原码就是:00000000 00000000 00000000 00001001  又因为是正数,,所以,原反补相同,补码也是这个二进制序列,,现在以%d打印,打印的是有符号的十进制整

,最高位就是符号位,符号位为0,代表一个正数,原反补相同,原码就是该二进制序列,打印出来就是9,整型在内存中是以补码的形式存储的,所以,上述的二进制序列是在内存中的

二进制序列,,也是补码,但是,如果以*pFoat的形式取出的话,是以float浮点型的形式拿出,,首先,浮点型会认为上述的二进制序列也是内存中的二进制序列, 即,上述二进制序列,

如果整型来看的话就是补码,如果浮点型来看的话,就不再考虑原反补的问题了,他是内存中的二进制序列,所以,浮点型也会认为内存中的二进制序列就是上述的二进制序列,站在浮点

数的角度去看这个二进制序列,也是内存中的,他会认为第一个0是符号位,接下来的8个比特位就是E,剩下的23个比特位就是M,,即:     00000000     00000000000000000001001

所以,S=0,,E全为0,,又因为是float的,所以,E的真实值就是1-127=  -126,,,,所以,E的真实值就是 -126 ,,又因E全是0,,所以就在M前面补个 0.  所以,M就是:

0.00000000000000000001001   ,所以,S=0,E= -126  M=0.00000000000000000001001   ,,还原出来的二进制浮点型数字就是:

(-1)^ 00.00000000000000000001001 * 2^ (-126)   ,,得到的就是:0.00000000000000000001001 * 2^ (-126) ,这就是我们的二进制浮点型的数字,,我们以%f 打印的话,要转为

十进制的浮点型数字才可以,,转完之后得到的是:  0.00000000000000000001001 ,小数点往左移动126位,,得到的就是:0.00000000000000000000000000.......0000000001001

 这就是我们的十进制的浮点型数字,而,%lf 和 %f 只能默认在屏幕上打印出小数点后6位,,所以,打印在屏幕上的就是:0.000000;

%f 和 %lf 都是以十进制的浮点型打印在屏幕上的;

接下来我们看:*pFloat = 9.0;

已知,,9.0是十进制浮点型数字,,我们要写出他在内存中的存储形式,就要先写出对应的二进制浮点型数字的科学计数法的形式,第一步就是先把该十进制的浮点型数字转为二进制的

浮点型数字,,得到:1001.0,,这就是二进制的浮点型数字,,又等于:1.001 * 2^3,等价于:(-1)^01.001 * 2^3,,,所以我们得到: S=0 ,M=1.001   E=3,,现在存到二进制

序列中,E要加上127=130,,得到的二进制序列就是: 0 10000010 00100000000000000000000,,这就是十进制浮点型数字9.0在内存中的存储形式,,因为是在内存中的,如果现在

%d整型的形式取出,并且又是在内存中的二进制形式,所以该二进制序列会被认为成是整型中的补码,,以%d有符号的十进制整型打印,,有符号位,最高位是符号位,最高位是0,

代表正数,原反补相同,所以,原码还是该二进制序列,,打印出来就是:1091567616,,我们知道,整型在内存中是以二进制补码的形式存储,但是以十六进制进行展示,还会涉及到

大小端字节序的问题,如果低位放在低地址上就是小端存储,低位放在高地址上就是大端存储,,那么,浮点型在内存中的存储形式会涉及到大小端字节序的问题吗?

我们知道,十进制浮点数9.0在内存中的存储形式是: 0100 0001 0001 0000 0000 0000 0000 0000,,写成十六进制即为:0x 41 10 00 00,,

 我们发现,,即使浮点型在内存中的存储形式不涉及到 原码,反码,补码的概念,浮点型有自己的存储方式,但是仍会涉及到大小端字节序的问题,并且还是小端存储,

我们已经知道,十进制浮点型数字9.0在内存中的存储二进制序列为:0 10000010 00100000000000000000000,,我们再以浮点型的形式取出,,首先,我们要从这个二进制序列中拿出

S M E ,,可得:S=0,,E=3,,M=1.001,,001后面的数省略,本来就是补上去的,拿出来的时候要再舍去,所以,得到科学计数法的表示形式为:(-1)^0 * 1.001 * 2^3,从而得到

1.001 * 2^3,,再得到:1001.0,,这就是二进制浮点型数字,,又因,%f 和 %lf都是以十进制浮点型打印的,所以转为十进制浮点型数字为:9.0,,不够小数点后6位,补齐即得:

9.000000,,这就是我们的答案;

 如果以%d整形的形式拿出来,就认为内存中存储的就是补码,,如果以浮点型的形式拿出来,就认为这内存里面存的就是浮点数类型,以浮点数的形式去还原出来;

 如果想要以浮点型存入,,整型取出的话,应该写成:

 而不是简单的写成;

这样输出的结果和我们的是不一样的,这是因为,我们以浮点型进行存入,,没有经过强制转换,出来的时候的 f 并不是以整型进行取出的,他还是以浮点型进行取出的,,只不过是

以%d打印而已,所以,像这种存入和取出不同类型的时候,一定要有强制转换,没有强制转化就不行;

用了一个下午才写完,如果屏幕前的您觉有对自己有所帮助,那就给up点赞收藏吧,多谢各位,您的鼓励才是我坚持的动力,感谢感谢!!~~~

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>