【C 语言】浮点数在内存中的存储(详解)
[TOC]
引言
本文首发于 ❄️慕雪的寒舍
markdown 语法小知识点
写在前面,markdown 语法的小知识点
如何实现文字变红且加上了底色?如 hello world
markdown 语法如下即可!
1 | `hello world` |
以及页内跳转
1 | <span id="jump">这一句话没啥用</span> |
正题
之前学习完了整形、字符类型在内存中的存储,今天让我们来看看 float 类型!
整数类型👉【int】
字符类型👉【char】
常见的浮点数
1 | 3.14159 |
浮点数家族包括 float
、double
、long double
类型。
而浮点数表示的范围是在头文件 <float.h>
里面定义的。
需要了解的是
如果你打出
3.14
,编译器默认是 double 类型的。若想让他为 float 类型,则要在前面加f
;1E10 是科学计数法,代表
1.0×10^10
代码引例
先来看看下面这串代码
1 | int main() |
运行的结果如下
指针 pfloat 保留的是强制转换为 float 类型的 int 变量 n
准确来说,是将 int 指针类型强制转换为了 float 指针类型
那打印的结果不应该是 9 吗?为什么是 0.000000 呢?
再来看看后面的代码,我们让 * pfloat=9.0,用 % d 打印的时候,却打印出了一串不知道怎么来的很大的数字。这又是为什么呢?
一个涉及到的小知识点
- 不管是 double 类型,还是 float 类型,默认小数点后都有 6 位
- 我们可以用 %.f 的方式来控制打印,如 %.3f 就是只打印到小数点后 3 位
答案只有一个:浮点型在内存中的存储方式和 int 类型完全不同!
浮点型如何在内存中存放?
根据国际标准 IEEE(电气和电子工程协会) 754,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S
表示符号位,当 S=0,V 为正数;当 S=1,V 为负数。M 表示有效数字,大于等于 1,小于 2。
2^E
表示指数位。
十进制 & 二进制的科学计数法
我们在小学就学到过,1.234×10^2=123.4
而二进制中,其实就是把底数的 10 变成了 2,1.011*2^2=101.1
记住以下这个结论即可
二进制码 M 乘以 2 的 n 次方,相当于将二进制码 M 的小数点向右移动 n 位
S\M\E 如何判断?
二进制的小数部分
0.1
表示十进制的0.5
,即 2 的 - 1 次方。根据小数点开始往右数的位数,依次为十进制 2 的 - 1、-2、-3… 次方
我们以 5.5
为例,它的二进制是 101.1
,带入上方提到的公式,相当于
和上面的公式比对,我们可以读出来 S=0,M=1.011,E=2
。
在之前的学习中,我们知道 float 类型占用 4 个字节的空间,而 double 类型则是 8 个字节
浮点类型的内存空间示意图
float 类型的 S\E\M 被分区存放在这 4 个字节的内存空间中
同理,double 类型的 S\E\M 也是分区存放,它的有效数字长于 float 类型
对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为有效数字 M
IEEE754 对 M 的特殊规定
因为是二进制数,1≤M<2
,而 M 可以写成 1.xxxxxx
的形式,其中 xxxxxx 是小数部分
IEEE 754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分
比如保存 1.01 的时候,只保存小数点后的 01,等到读取的时候,再把第一位的 1 加上去
这样做的目的是节省 1 位有效数字。以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存 24 位有效数字。
我在学习的时候,关于这个 24位有效数字
曾产生了疑惑。
实际上它并不难理解:
在内存中,32 位浮点数的 M 有 23 位的空间,如果我们保存了小数点前面的 1,就只能保存小数点后 22 位的内容。
但如果我们省略 1,只保留小数点后的内容,那不就能保存到小数点后第 23 位了吗?再加上原来小数点前的 1,不就是 24 位有效数字了!
IEEE754 对指数 E 的特殊规定
E 是一个无符号整数 (unsigned int)
- 如果 E 为 8 位,它的取值范围是 0-255
- 如果 E 为 11 位,它的取值范围是 0-2047
可是科学计数法里面的 E 是可以出现负数的。
所以 IEEE754 规定,存入内存时 E 的真实值必须再加上一个中间数
- 8 位的 E,中间数是 127
- 11 位的 E,中间数是 1023
例:
2^10
的 E 是 10,所以保存为 32 位浮点数的时候,E 必须保存为10+127=137
,即10001001
。保存为 64 位浮点数的时候,E 保存为
10+1023=1033
,即10000001001
①当 E 不为全 0 或全 1 时
浮点数采用下面的规则来进行存放:
内存中指数 E 的计算值减去 127 (或 1023),得到 E 的真实值,再将有效数字 M 前面加上第一位的 1
以 32 位浮点数举例:
0.5 的二进制形式为 0.1。科学计数法中整数部分必须为 1,小数点应右移一位。
则为 1.0*2^(-1)
,E 的真实值为 -1
,存放在内存中为 -1+127=126(01111110)
M 存放小数点后的 0,补全 23 位,全为 0
这时候 0.5 的二进制表现形式就是
1 | 0 01111110 00000000000000000000000 |
对应 S、E、M 的部分如下表所示
S | E | M |
---|---|---|
0 | 01111110 | 00000000000000000000000 |
②当 E 为全 0 时
这时,浮点数的指数 E 等于
1-127
(或者1-1023
)即为真实值有效数字 M 不再加上第一位的 1,而是还原为
0.xxxxxx
的小数这样做是为了表示
±0
,以及接近于 0 的很小的数字
③当 E 为全 1 时
当 E 为全 1 时,原 E 为 128,数字非常大,相当于无穷大
这时,如果有效数字 M 全为 0,表示 ± 无穷大(正负取决于符号位 s)
解释开篇代码
整形 9 的原码如下
1 | 00000000 00000000 00000000 00001001 |
当我们将它强制存放到 float 类型的指针中时
S | E | M |
---|---|---|
0 | 00000000 | 0000000 00000000 00001001 |
解码出来就是
$$
(-1)^00.0000000 00000000 000010012^{-126}
$$
这是一个很小的数字,远小于 float 类型默认的小数点后六位,所以 printf 打印的是 0.000000
1 | int n; |
这里 9.0 就是以浮点数的形式存入 float 指针的
- 9.0 十进制
- 1001.0 二进制
1 | S=0, M=1.001, E=3 |
开启调试,在内存框中查看 n 的地址如下
41 | 10 | 00 | 00 |
---|---|---|---|
0100 0001 | 0001 0000 | 0000 0000 | 0000 0000 |
正好对应了浮点数 9.0 在内存中存放的二进制码
最后 n 以 %d
整形的方式打印出来,就是我们看到的 1091567616
结语
考试周快要结束啦!寒假将开始新的代码学习
终于补上了之前欠下的博客了,当作是一种复习吧,的确有不少东西已经忘记的差不多了😥
感谢你看到最后,点个赞再走吧!