那么好了好了,宝子们,今天给大家介绍一下 “数据在内存中储存” 的来龙去脉---浮点数,来吧,开始整活!⛳️
一、数据类型家族(浮点数)
家族成员:float,double,long double
二、浮点数在内存中的存储
1.抛砖引玉 :
通过下面的例题一,我们来引入浮点数在内存中的存储是怎么样的?
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("n的值为:%d\n", n);
printf("p的值为:%f\n", *p);
*p = 9.0;
printf("n的值为:%d\n", n);
printf("p的值为:%f\n", *p);
return 0;
}
当我们没有运行,咱们可以猜想一下这4个结果分别是什么呢?
我相信很多小伙伴和我当时想的一样,那么就是:
9
9.000000
9
9.000000
是这个样子的,但是我们看一下实际运行结果是什么样的?
结果和我们想的不一样,错了一半,为什么呢?我们接下来就开始解释一下这个浮点数在内存中到底是怎样存储的?
三、浮点数存储的关键表达式:浮点数(v)=(-1)^S*M*2^E
1.
根据国际标准
IEEE
(电气和电子工程协会)
754
,任意一个二进制浮点数
V
可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当
s=0
,
V
为正数;当
s=1
,
V
为负数。
M表示有效数字,大于等于
1
,小于
2
。
2^E表示指数位
2. 我们通过举例来进行对表达式的剖析:
十进制:5.5
二进制:101.1(小数点前的最后一个数字的权重是2^0,那么小数点后按顺序来说就是2^(-1)
,2^(-2)……以此类推。)
转化:1.011*2^2---->(-1)^0*1.011*2^2(S=0,M=1.011,E=2)
接下来我们以float为数据类型来讲解一下浮点数在内存中的存储:
表达式中的字母顺序是S M E,但是浮点数在内存中存储的顺序是S E M
对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
但是别看的这么简单,还有一个特别需要注意的点,就是E和M的储存都有特殊情况:
3.
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
位有效数字。(在这种情况下可以提高数据的精度)
4. 至于指数E,情况就比较复杂:(E的存入)
首先,E
为一个无符号整数(
unsigned int
)
这意味着,如果E
为
8
位,它的取值范围为
0~255
;如果
E
为
11
位,它的 取值范围为0~2047
。但是,我们知道,科学计数法中的
E
是可以出现负数的,所以
IEEE 754
规定,存入内存时E的真实值必须再加上一个
中间数(无论E是正负,都需要加上中间数来修正)
对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
比如,2^10
的
E 是10
,所以保存成
32
位浮点数时,必须保存成
10+127=137
,即
10001001。
5.
(E的取出):
指数
E
从内存中取出还可以再分成三种情况:
(1)E
不全为
0
或不全为
1
这时,浮点数就采用下面的规则表示,即指数
E
的计算值减去
127
(或
1023
),得到真实值,再将有效数字
M
前(还原)
加上第一位的
1
。 比如:
0.5
(
1/2
)的二进制形式为
0.1
,由于规定正数部分必须为
1
,即将小数点右移
1
位,
则为
1.0*2^(-1)
,其阶码为
-1+127=126
,表示为
01111110
,而尾数
1.0
去掉整数部分为
0
,补齐
0
到
23
位
00000000000000000000000
,则其二进制表示形式为
0 01111110 00000000000000000000000
(2)E
全为
0
这时,浮点数的指数
E
等于
1-127
(或者
1-1023
)即为真实值,
有效数字
M
不再加上第一位的
1
,而是还原为
0.xxxxxx
的小数。这样做是为了表示
±0
,以及接近于
0
的很小的数字。
(3)E
全为
1
这时,如果有效数字
M
全为
0
,表示
±
无穷大(正负取决于符号位
s
);
好了,关于浮点数的表示规则,就说到这里。
四、首尾呼应(对前面例题的解释)
对前半部分的分析:
int n = 9;
float* p = (float*)&n;
printf("n的值为:%d\n", n);//9(在这一步我们以整数的形式放进n里面,再以%d整形来打印出来,完全没有问题,答案就是9)
printf("p的值为:%f\n", *p);//0.000000
/*9------>0 0000000000000000000000000001001(二进制)
当我们以浮点数的视角来打印出来的时候,我们要分别求出来S M E。
0 00000000 00000000000000000001001
S E M
1.当E是全0的时候,E=1-127即为真实值。
2.有效数字M不再加上第一位的1,而是还原为
0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
浮点数v=(-1)^0*0.00000000000000000001001*2^(-126)
因为以%f的形式打印出来浮点数,最终的结果只有小数点的后6位 : 0.000000*/
对后半部分的分析:
*p = 9.0;
printf("n的值为:%d\n", n);//1091567616
/*因为上一部分的代码*p是浮点数,所以我们以浮点数的形式储存9.0。
9.0
1001.0=1.001*2^3
又因为9.0是正数,所以s=0
9.0==(-1)^0*1.001*2^3
内部的储存结果如下:
0 10000010 00100000000000000000000
S E M
当我们以整形的形式打印出来的时候,又因为它是个正数,所以说打印出来的时候正数的原码反码补码相同:
最终的结果直接就是他:01000001000100000000000000000000
然后再以二进制读出来:1091567616*/
printf("p的值为:%f\n", *p);//这个地方等于9.000000没有疑问
好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!