【数据的存储】浮点数在内存中的存储详解【超详细的保姆级别教程,让面试官心服口服】手撕浮点数存储使用方式
【数据的存储】浮点数在内存中的存储详解【超详细的保姆级别教程,让面试官对你心服口服】手撕浮点数存储使用方式
作者: @小小Programmer
这是我的主页:@小小Programmer
在食用这篇博客之前,博主在这里介绍一下其它高质量的编程学习栏目:
数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏:算法 这里可以说是博主的刷题历程,里面总结了一些经典的力扣上的题目,和算法实现的总结,对考试和竞赛都是很有帮助的!
先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力。看完之后别忘记关注我哦!️️️
本篇建议收藏后食用~
- 注:本期博客内容可能不会使得我们写代码的能力提高,但是,这期博客是学习计算机深层次的知识,是内功的修炼,学会这些内容,我们才能在面试笔试场上游刃有余,脱颖而出!
引入
我们来看这个代码,伙伴们可以思考一下,输出的结果是什么。
按照我们以往学习的经历和感觉,有伙伴会说,是四个9。
然而答案是:
为什么会出现0.000000和这个1091567616? 下面就跟随博主的步伐,了解并学习浮点数在内存中的存储,我们就能明白了。
浮点数的存入
n
和*pFloat
明明是同一个数,为什么浮点数和整数解读会有这么大差距,这表明,浮点数和整数放进去,拿出来的方式是不一样的。根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式
(-1)^ S×M×2^E
(-1)^S表示符号位,当S=0时,V为正数;当S=1时,V为负数
M表示有效数字,大于1小于2
2^E表示指数位具体S、M、E代表什么,我们来举一些例子
十进制的5.0:
写成二进制就是101.0,注意,小数点后的0代表是2^-0
所以101.0可以表示成1.01*2^2
因此我们可以得出S=0(代表是为正),M=1.01(代表有效数字),E=2(代表指数位位2)
所以5.0可以写成二进制的
(-1)^0乘1.01乘2的2次方十进制的-5.5:
写成二进制就是-101.1。注意,小数点后的1代表2的负一次方。
-101.1可以写成-1.011*2^2
所以S=1(代表是负数),M=1.011(代表有效数字),E=2(代表指数位为2)。
所以-5.5可以写成:
(-1)^1乘1.011乘2的2次方
了解完浮点数如何改写成二进制的规定形式之后,接下来,S,M,E这些数字如何放进内存里面呢?
IEEE 754规定
对于单精度浮点数(比如float
类型),也就是占4个字节的浮点数(1个字节8个比特位),也就是32为浮点数:
最高位存S,接着的8位存E,剩下的23位存M
图片来自于比特就业课:
IEEE 754规定
对于双精度浮点数(比如double
类型),也就是占8个字节的浮点数(1个字节8个比特位),也就是64为浮点数:
最高位存S,接着的11位存E,剩下的52位存M
图片来自于比特就业课:
对于有效数字M,和指数E,在存入的时候还有一些特殊的规定:
对于有效数字M:
首先M是二进制数字,而且通过移动小数点的位置,我们得到的有效数字M肯定是小于2大于1的,即1<=M<2,
因此,有效数字M可以表示成1.xxxxxxx
的形式。
即,存入的时候,小数点左边的1需要存吗,没有必要,因为是固定的1,我们不需要浪费一个比特位存这个固定的值,因此,我们只需要存后面的xxxx即可。
将第一位舍去之后,相当于在32位下我们可以保存24位有效数字
对于指数E,就相对比较复杂了: 首先,E的类型是无符号整数(
unsigned int
类型),在32位下它的范围是0 ~ 255(因为8个比特位,全1时为11111111,表示255,全0时表示0),在64位下它的范围时0 ~ 2047。
看到这里,有伙伴会问了,既然E是unsigned int
类型,但是E是有可能时负数的啊?
比如十进制0.5 它的二进制就是0.1 也就是1.0*2^-1,那正负号怎么表示呢? 因此,IEEE 754规定:
在存入E的时候,必须对E的真实值再加上一个中间数
。 在32位下,这个中间数是127,在64位下这个中间数是1023。 比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137。
看到这里,可能很多伙伴都懵掉了,因此,我们来举几个例子即可:
我们可以用这个代码测试,打开内存,我们就可以看我们算的对不对了。
int main() {
float f = 5.5;
float f2 = 0.5;
return 0;
}
对于5.5:
首先:
可以写成101.1
也就是1.011*2^2
所以,此时S=0,E=2,M=1.011
所以存进去的时候,S存0
,E存2+127=129(1000 0001
),M存011
也就是
0 10000001 011
后面还有20位怎么办:补0即可
所以结果是:0 10000001 01100000000000000000000
这串数字我们四个四个分开,改成16进制
0100 0000 1011 0000 0000 0000 0000 0000
也就是
0x40b00000
,ok得出答案,我们f10调试,打开内存
果然是,0x40b00000
,这样,我们不久搞清楚存进去的方式了吗
第二个数0.5,伙伴们可以自己动笔算一下,算出来再对一下答案哦!
浮点数的取出
浮点数在取出的时候,还要分为三种情况讨论:
- 情况1:E不为全0或不为全1时:
此时,取出的方式和存进去的相反,怎么存怎么取。 将指数E减去127(或1023),得到真实值,然后再将有效数字M前加上第一位的1。 - 情况2:E为全0:
此时,浮点数的指数E等于1-127或(1-1023)得到真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxxx
的小数。这样其实时为了表示±0这个数,因为当E的比特位上全部都是0的时候,其实原来的数字是带着2的负126次方或2的负1023次方的,是一个很小的数字,所以计算机直接将其解析为0。 - 情况3:E为全1
同理,和全零类似,全1的数字带着2的126次方的指数(或1023次方),是个很大的数字,当计算机看到全1的时候,直接解析成无穷大。
引入的解释
其实看到这里,我们对浮点数在内存中的存储已经了解地非常深入了,这些知识储备足够我们应付笔试和面试官的提问。
接下来,我们看回这题:
现在,用我我们所学的方法,解释那个答案是怎么出来的。
首先,9以整型的形式存入内存: 先翻译成原码:
00000000000000000000000000001001
~9的原码
然后因为是正的,原反补相同 所以存进内存的9的补码为:
00000000000000000000000000001001
~9在内存中的形式 接下来我们以浮点数的形式取出来
我们先划分一下这32个bit位,划分出S,E,M的位置先:
0 00000000 00000000000000000001001
~被解析成浮点数、
此时的E,我们发现是一个全0,说明这个9被看成浮点数的时候,是一个无限接近于+0的数字,而由于精度有限制,所以打印的就是0.000000
紧接着,我们看当9.0以浮点数的形式存入的时候: 转化成二进制,即
1001.0
即:(-1)^0 * 1.001 * 2^3
此时:S=0、M=1.001、 E=3 +127 =130~10000010
因此存进内存的是:
0 10000010 00100000000000000000000
当这个数被看作整数以%d的形式
打印的时候,由于符号位为0。原反补相同,因此,01000001000100000000000000000000
直接被解析成整数,换算成10进制就是1,091,567,616
,这正是我们打印出来的结果!
所以,搞懂浮点数在内存中存储之后,我们就可以很好地解释那个结果了。
尾声
虽然本期博客内容可能不会使得我们写代码的能力提高,但是,这期博客是学习计算机深层次的知识,是内功的修炼,学会这些内容,我们才能在面试笔试场上游刃有余,脱颖而出!
因此,在临走之前,不要忘了你的点赞关注和收藏哦!