【浮点数】在内存中如何存储???简单理解

2024-07-20 笔记 - 3

2024-09-24 修改

作者(Author):郑龙浩 / 仟濹(网名)

目录

浮点型在内存中的存储

① 【整型】的存储和获取的方式 与 【浮点型】的存储和获取的方式是 “不一样” 的

下面举例:

浮点存储氛围三部分

② 浮点数 的存储规则

要先了解 国际标准 IEEE(电气和电子工程协会)754,浮点数用 V 来表示的。

如何更好的理解呢???

③ IEEE 754 的< 特别规定 > 针对于 “有效数字M”和“指数E”

④ 为什么浮点数总是不那么精准呢???

⑤ 疑问:E是无符号的那怎么表示负数呢???

① E 有 1 也有 0 时

② E 全为 0 时(特殊情况)

③ E 全为 1 时(特殊情况)


浮点型在内存中的存储

32位的存储

【浮点数编码 / 浮点数表示】 中有 S(符号位)、E(指数位)、M(尾数位或有效数字)

  • float 32:    S - 1bit、E - 08bit、M - 23bit
  • double 64:S - 1bit、E - 11bit、M - 52bit
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 
 int main()
 {
     int n = 9;
     float* pFloat = (float*)&n;
     printf("n的值为:%d\n", n);//9 以整型的形式(9)存入n(int),还以整型的形式肯定可以拿出来
     printf("*pFlaot的值为:%f\n", *pFloat);//出错,打印出来是不对的 --> 以整数的形式(9)存进去,肯定不能以浮点的形式拿出来
     *pFloat = 9.0;
     printf("n的值为:%d", n)//出错,打印出来是不对的 --> 以浮点的形式(9.0)存入(int),以整型的形式拿不出来
     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 表示指数位

如何更好的理解呢???

举例:

 十进制 - 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.×× 吗???

M永远不会是 0.××,它百分百是1.××

 十进制 - 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是【无符号】的那怎么表示【负数】呢???

首先必须先知道两个概念:

  • 真实指数 / 实际指数 - 人读的数

  • 存储指数 / 编码指数 - 存入内存中的数

存: 将【真实指数 / 实际指数】计算成【存储指数值 / 编码指数值】存入到内存中。(eg:-1 计算成126)

以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 - 1271 - 1023得出【实际指数】【-126】【-1022】

  • E的【实际指数】126 / 1022

  • M有效数字不是加上1了,而是加上0,即还原成 0.×××××××,这用作是为了表示±0.××这种正负无穷的无限接近于0的数字。

③ E 全为 1 时(特殊情况)

有效数字全为1,表示±无穷大

因为当E全都为1 的时候,E的【存储指数值】是255,那么【实际指数】就是128,2 ^ 128 已经是一个非常非常大的数了,所以直接表示无穷大就可以了。

总之简单来说就是:

  1. 存储时,我们把实际的指数(可能是负数)加上一个固定的数(偏移量),这样就变成了一个正数。

  2. 当我们读取这个数时,我们再把这个正数【存储指数值】减去那个固定的数,这样就得到了原来的指数值,可能是负数。

通过这个方法,即使我们用无符号的数(只能表示正数)来存储指数,我们也能正确地处理正数和负数的指数。

本人小白,在学习中,这就当做笔记了,可能很多不对的地方,请网友们提出更改意见哈哈。

你可能感兴趣的:(C语言学习笔记,算法,c语言,数据结构,笔记,学习,java,python)