2024-07-20 笔记 - 3
浮点型在内存中的存储
32位的存储
V【浮点数编码 / 浮点数表示】 中有 S(符号位)、E(指数位)、M(尾数位或有效数字)
float32位存储 | 比特位数 | double64位存储 | 比特位数 |
---|---|---|---|
S | 1bit | S | 1bit |
E | 8bit | E | 11bit |
E | E | ||
E | E | ||
E | E | ||
E | E | ||
E | E | ||
E | E | ||
E | E | ||
M | E | ||
M | E | ||
M | E | ||
M | 23bit | M | 52bit |
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M | M | ||
M - 到这里共23bit | M | ||
…52个bit位 |
64位的存储
常见的浮点数
普通写法 3.1415926
科学技术 1E10
① 【整型】的存储和获取的方式 与 【浮点型】的存储和获取的方式是 “不一样” 的
如果以浮点数的形式存进去就要以浮点数的形式拿出来;
如果以整型的形式存进去就要与整型的形式拿出来。
如果用整型的形式存进去,用浮点的形式拿出来,拿出来会出问题;
如果以浮点的形式存进去,用整数的形式拿出来,拿出来会出问题。
下面举例:
n 和 *pFloat 在内存中虽然是同一个数,但是一个是【整型】的取法,一个是【浮点】的取法,解读结果一定差别很大。
#include <stdio.h> int main() { int n = 9; float* pFloat = (float*)&n; printf("n的值为:%d\n", n);//9 以整型的形式(9)存进去,还以整型的形式肯定可以拿出来 printf("*pFlaot的值为:%f\n", *pFloat);//出错,打印出来是不对的 --> 以整数的形式(9)存进去,肯定不能以浮点的形式拿出来 *pFloat = 9.0; printf("n的值为:%d", n)//出错,打印出来是不对的 --> 以浮点的形式(9.0)存进去,以整型的形式拿不出来 printf("*pFloat的值为:%f\n", *pFloat);//9.0 --> 以浮点的形式(9.0)存进去,肯定可以以浮点的形式再拿出来 }
浮点存储氛围三部分
比如int和flaot,以整型的形式进行存储的时候, int的32个bit位除了开头的符号位,其余31位全都是用来表示数值的。 而在float中,分成了三块,
32位
-
S【符号位】占1bit,表示符号
-
E【指数位】占8bit,可以理解为表示科学技术法中E后面的数字,E在内存中存储的值叫做【存储指数值】,E表示的数值叫做【实际指数】
-
M【尾数 / 有效数字】占23bit,可以理解为表示科学计数法中E前面的数字
64位
-
S【符号位】占1bit,表示符号
-
E【指数位】占11bit,可以理解为表示科学技术法中E后面的数字,E在内存中存储的值叫做【存储指数值】,E表示的数值叫做【实际指数】
-
M【尾数 / 有效数字】占52bit,可以理解为表示科学计数法中E前面的数字
后面会进行解释
② 浮点数 的存储规则
要先了解 国际标准 IEEE(电气和电子工程协会)754,浮点数用 V 来表示的。
(-1)^S * M * 2^E
(-1)^S 表示符号位,当 S = 0,V 为正数;当 S = 1,V 为负数
M 表示有效数字,范围为
1 <= M < 2
2^E 表示指数位
如何更好的理解呢???
举例1:
十进制 - 5.0 二进制 - 101.0 --> 1.01 * 2^2 V格式 - S = 0 M = 1.01 E = 2 == (-1)^S * M * 2^2
举例2:
【小数部分】转换成二进制的方式和【整数部分】装换成二进制的方式不一样
整数部分:计算的时候次幂是从【0】开始的
101 –> (1 * 2^2) + (0 * 2^1) + (1 * 2^0) --> 5小数部分:计算的时候次幂是从【-1】开始的
0.1 --> 1 * 2^-1 --> 0.5 0.101 --> (1 * 2^-3) + (0 * 2^-2) + (1 * 2^-1) --> 0.125 + 0.25 + 0.5 = 0.875
十进制 - 5.5 二进制 - 101.1 --> 1.011 * 2^2 V格式 - S = 0 M = 1.011 E = 2 == (-1)^S * M * 2^2
注: 即使是0.××M不会出现0.××
十进制 - 0.5 二进制 - 0.1 V表示 - S = 0, M = 1.0, E = -1 == (-1)^0 * 1.0 * 2 指数在内存中其实并不是存储的-1,后面会讲
③ IEEE 754 的< 特别规定 > 针对于 “有效数字M”和“指数E”
-
有效数字 M
首先我们知道M的取值范围是,1 <= M < 2
(M是不可能大于2的,因为二进制表示的小数全是 1 或 0,不存在大于2的情况),M一般是1.××××××××××××××××××××××
(22个×,1个1 共23位)
IEEE 754 规定,在计算机内部保存M的时候,默认这个数的第一位总是1,因此可以被舍去,只保存后面的××××××××××××××××××××××××
(23个个×,就不存1了,这样子就会节省出来,相当于间接的存储了开头的1,可以理解为就间接的存储了24位bit数)这一部分,这样开头一位就可以不占用空间了,从而多出来一位。
比如1.1101(二进制表示),只存储1101,等到读取的时候,再把第一位的1加上去,这样可以节省一位“有效数字” ,使其多了一位可以表示有效数字的bit位。
疑惑:M中的小数点算一位吗??? 不算
④ 为什么浮点数总是不那么精准呢???
有一些可以的出准确的浮点数,有一些却不可以,和二进制存储有关系,后面的位数可能就是差那么一点点就能算出来,所以好多时候算出来的都是近似值,总会丢一些二进制位。
-
指数 E
注: E 为无符号整数unsigned int。
32位的E 的 偏移量 - 127 64位的E 的 偏移量 - 1023
⑤ 疑问:E是无符号的那怎么表示负数呢???
存: 将【真实指数 / 实际指数】计算成【存储指数值 / 编码指数值】存入到内存中。
以32位的float为例
E因为是无符号的,所以肯定不可能再存入负数了,只能进行其他的“转换”进行存储。
这里就出现了一个东西,叫做“中间数”,可以让负数进行偏移,这样就做到了将负数偏移成可以存入无符号内存中的正数。
比如想存入指数值E是 -1,我们计算肯定是按照-1【真实指数 / 实际指数】计算的,可是内存中可不是这么存储的,在存入内存的时候,用了一种奇特的方式,即存入 -1 + 127(-1 + 偏移量),叫做【存储指数值 / 编码指数值】所以是以数值126存储到指数部分的。
读: 当这个浮点数被读取并应用于计算时,计算过程会从存储的指数值126 - 127(偏移量) = -1 然后将可以将这个恢复后的指数值-1正确的参与到后续的浮点数计算中。
读取的三种方式 - 其实也会影响 M 中的开头位的值
① E 有 1 也有 0 时
【存储指数值】 - 【偏移量 / 中间值】 = 【实际指数】 通过这种计算就可以将存储到内存中的【存储指数值】正确的读取出来,读取出来的值就是【实际指数】
下面用32位的float为例进行解释,也为了进一步加深自己的理解
5.5 101.1 S = 0 M = 1.011 E = 2 E - 00000010【实际指数】 - 2 E - 10000001【存储指数值】 - 129 == 2 + 127 下面写的是该浮点数在内存中正确的存储 0 10000001 01100000000000000000000 - 总共32位 S = 0, E = 10000001, M = 01100000000000000000000 总之读取的时候,E其实转换成00000010了,就是直接减去127
总而言之就是计算的时候要将存储的指数读取位实际指数进行计算。
总结:
-
将E的从内存中存储的【存储指数值】 - 【偏移量】 = 【实际指数】读取出来
-
将有效数字M的开头位置在加上 1,还原成
1.×××××××
-
进行计算,计算公式:
**(-1)^S * M * 2^E**
,得出最终的【浮点数】
② E 全为 0 时(特殊情况)
其实是 -127 + 127
E全为0的时候,E的【存储指数值】可是-127,但是这种的统一按照存储指数为【1】来的,1 - 127
或1 - 1023
得出【实际指数】为【-126】或【-1022】
-
E的【实际指数】126 / 1022
-
M有效数字不是加上1了,而是加上0,即还原成
0.×××××××
,这用作是为了表示±0.××
这种正负无穷的无限接近于0的数字。
③ E 全为 1 时(特殊情况)
有效数字全为1,表示±无穷大
因为当E全都为1 的时候,E的【存储指数值】是255,那么【实际指数】就是128,2 ^ 128 已经是一个非常非常大的数了,所以直接表示无穷大就可以了。
总之简单来说就是:
-
存储时,我们把实际的指数(可能是负数)加上一个固定的数(偏移量),这样就变成了一个正数。
-
当我们读取这个数时,我们再把这个正数【存储指数值】减去那个固定的数,这样就得到了原来的指数值,可能是负数。
通过这个方法,即使我们用无符号的数(只能表示正数)来存储指数,我们也能正确地处理正数和负数的指数。
本人小白,在学习中,这就当做笔记了,可能很多不对的地方,请网友们提出更改意见哈哈。