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

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

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

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

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

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

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

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

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

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

​首先要知道: 四舍六入五成双

这只适用于保留到小数点后几位的情况,即,保留小数点后几位的位数大于等于1的情况,即,有小数点的情况,遵循四舍六入五成双,

如果是保留到小数点后0位,或者理解成,保留到整数的情况,即遵循四舍五入,要区分开;

所谓四舍六入就和平常所说的是一样的:

1、四舍:

浮点型如何在内存中进行存储呢?_第1张图片

2、六入:

浮点型如何在内存中进行存储呢?_第2张图片

3.五成双:

所谓五成双,即指,如果我们要保留两位小数,那么就观察小数点后第三位,如果是小于等于4,则舍去,如果是大于等于6,则进一位,如果小数点后第三位等于5的话,就要再去观察该位的前一位,即小数点后第二位是不是偶数,是偶数则进一位,是奇数则不进,其中,0我们当做偶数来看;

(1)前一位为奇数:

浮点型如何在内存中进行存储呢?_第3张图片

(2)前一位为偶数:

浮点型如何在内存中进行存储呢?_第4张图片

我们把0当做一个偶数,也要进一位:

浮点型如何在内存中进行存储呢?_第5张图片

如果是保留到小数点后0位,或者说,保留到整数的话,要遵循四舍五入的规则:

比如:

浮点型如何在内存中进行存储呢?_第6张图片

再或者:

浮点型如何在内存中进行存储呢?_第7张图片

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

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

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

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 ,那么,最终的结果会是什么样呢?

浮点型如何在内存中进行存储呢?_第8张图片

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

我们观察第一个结果,,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

浮点型如何在内存中进行存储呢?_第9张图片

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

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

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

法二:

二进制的浮点数:

浮点型如何在内存中进行存储呢?_第10张图片

所以说,十进制的浮点数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类型来说:

浮点型如何在内存中进行存储呢?_第11张图片

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

浮点型如何在内存中进行存储呢?_第12张图片

 通过上图,我们就知道,浮点型家族的三个类型对应的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的取值问题了;
到此为止,我们已经研究完了浮点型在内存中是如何存储以及如何取出的,接下来我们再返回去看一下我们的例题:
浮点型如何在内存中进行存储呢?_第13张图片

首先,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,,

浮点型如何在内存中进行存储呢?_第14张图片

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

我们已经知道,十进制浮点型数字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整形的形式拿出来,就认为内存中存储的就是补码,,如果以浮点型的形式拿出来,就认为这内存里面存的就是浮点数类型,以浮点数的形式去还原出来;

浮点型如何在内存中进行存储呢?_第15张图片

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

浮点型如何在内存中进行存储呢?_第16张图片

 而不是简单的写成;

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

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

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

你可能感兴趣的:(java,开发语言,后端,c语言)