目录
摘要:
一:题目
二:浮点数存储规则
1:浮点数表示公式
2:存储规则
三:读取浮点数取
1:常规情况
2:E全为0
3:E全为1
四:讲解题目
五:浮点数的特性
1:不能精确保存
2:不能直接比较
摘要:
本质旨在介绍浮点数在内存中的存储机制,再讲解与浮点数存储相关题目,最后解析浮点数的特性
一:题目
#include <stdio.h> int main() { int n = 9; float* pFloat = (float*)&n; printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); *pFloat = 9.0; printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }解释:在未了解浮点数在内存中的存储规则之前,可能会觉得打印:9,9.0,9,9.0;
二:浮点数存储规则
1:浮点数表示公式
任意⼀个⼆进制浮点数V可以表示成下⾯的形式:
V = (−1) S ∗ M ∗ 2^E
•(−1) S 表示:符号位,当S=0,V为正数;当S=1,V为负数
•M 表示:有效数字,M是大于等于1,小于2的
•2^E 表示:指数位
下面以浮点数5.0为例,十进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2
那么,按照上⾯V的格式,可以得出S=0,M=1.01,E=2 ----> V=(-1)0*1.01*2^2
十进制的-5.0,写成⼆进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
那么,按照上⾯V的格式,可以得出S=1,M=1.01,E=2 ----> V=(-1)1*1.01*2^2
所以,现在我们就可以用V=(-1)0*1.01*2^2表示浮点数5.0,用V=(-1)1*1.01*2^2表示-5.0,而所谓的浮点数在内存中的存储,就是将浮点数对应公式中的S,E,M存储进表示浮点数的4/8个字节中
注:V = (−1) S ∗ M ∗ 2 E这个公式就类似于二进制的科学计数法,根据科学计数法的规定,M必须是大于等于1,小于2
2:存储规则
对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
对于M和指数E在内存中的存储,还有⼀些特别规定!
M的规定:
前面说过,1 ≤ M < 2,也就是说,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。所以 IEEE 754 规定,在计算机内部保存 M 时,因为这个数 M 的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。
比如保存 1.01 的时候,只保存 01,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效数字。以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存 24 位有效数字,多一位保存,也会让精确度更高。
E的规定:
至于指数 E,情况就比较复杂。首先,E 为一个无符号整数(unsigned int),这意味着,如果 E 为 8 位,它的取值范围为 0~255;如果 E 为 11 位,它的取值范围为 0~2047。但是,我们知道,科学计数法中的 E 是可以出现负数的(负指数),所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数:对于 8 位的 E,这个中间数是 127;对于 11 位的 E,这个中间数是 1023。比如,2^10 的 E 是 10,所以保存成 32 位浮点数时,必须保存成 10+127=137,即 10001001。
三:读取浮点数取
所以知道了第二点中的存储规则,我们就知道怎么读取浮点数了!
1:常规情况
比如:0.5 的公式表示为:1.0*2^(-1),此时S为0,M为1.0,E为-1
存储的时候:
E:-1+127(中间值)=126,表示为01111110
M:1.0去掉整数部分为0,补齐0到23位 00000000000000000000000
则其二进制表示形式为:
此时我们读取也很容易:即指数E的计算值减去127,得到真实值-1,再将有效数字M前加上第⼀位的1。就得到了1.0*2^(-1),也就是0.5
但是除了常规情况,还有两种特殊情况,特殊情况不做深究,本博客只需理解常规浮点数即可
2:E全为0
E全为0,代表原本的E值为-126(或者-1023),当指数都是-126的时候,此浮点数一定是无限接近于0的浮点数!所以,M不再加上第⼀位的1,舍弃了1,直接还原为0.xxxxxx的⼩数。这样做是为了表示±0,以及接近于0的很小的数字。
0 00000000 00100000000000000000000
3:E全为1
同理,不过当E全为1的时候,E已经很大了,表示无限大的数字,这时,如果有效数字M全为0,表示±⽆穷大(正负取决于符号位s)
0 11111111 00010000000000000000000
注:有效数字M全为0且E全为1 ,直接就是无穷大,而当E全为1但M不全为0的时候,则为NaN
可以把 E 全为 1 想象成 一张“特殊情况卡”:
你看到这张卡,就知道:接下来的规则完全变了。
如果卡片背面(M)干干净净(全 0) → 意思是“无穷大”
如果卡片背面(M)写了点东西(不全 0) → 意思是“NaN”(里面甚至可以存具体是哪种 NaN,但通常不关心)
NaN 是 Not a Number 的缩写,直译就是“不是一个数”。一句话解释:NaN 用来表示“无效的、未定义的、或者无法用实数表示”的计算结果。
四:讲解题目
#include <stdio.h> int main() { int n = 9; float* pFloat = (float*)&n; printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); *pFloat = 9.0; printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }解释:
①:*pFloat的值为:0.000000
首先,9是一个整形,那其在内存中的32位就是:0000 0000 0000 0000 0000 0000 0000 1001,此时我们*pFloat,其是浮点数指针类型,所以此时系统会以浮点数的角度去看待这32位,会以浮点数的形式去读取这32位
所以此时E就是全0,2^E中的E为-126,按照特殊情况的规定,此数判定为无限小,其次M为0000 0000 0000 0000 0000 1001,须知,M没有23位是向后补0到23位,这里的0是前导0,代表这不是补的0,而是小数的位数,小数点后有23位,并且E为全0时,此时M不再加上第一位的1,而是还原为0.xxxxxx的小数。所以为0.00000000000000000001001
所以浮点数的值为:(-1)^0 * 0.00000000000000000001001 * 2^-126
这个数无限趋近于0了,而%f打印浮点数,最多展示小数点后6位,所以打印结果为0.000000
②:n的值为:1091567616
*pFloat = 9.0;此时32位是按照浮点数的存储规则存储进去的,然后%d打印n,此时会以解读整形4个字节的视角去看待这32位
9.0的公式表示为:1001.0-------->(-1)^0 * 1.001 * 2^3,所以s=0,E=3,M=1.001,此时存入:E=3,要加上127 = 130;所以存入的32位: 0 10000010 00100000000000000000000,此时%d的形式去打印 ,这32位补码符号位为0,视为整数,原反补相同,所以补码对应的值就是结果,1091567616。
五:浮点数的特性
1:不能精确保存
浮点数是无法在内存中精确保存的!
二进制小数每一位是:
第1位 = 1/2 = 0.5
第2位 = 1/4 = 0.25
第3位 = 1/8 = 0.125
第4位 = 1/16 = 0.0625
第5位 = 1/32 = 0.03125
所以一个浮点数,我们只能用这些位去组合表示,这就导致:
| 十进制 | 二进制小数 | 是否精确 |
|---|---|---|
| 0.5 | 0.1 | ✅ 精确 |
| 0.25 | 0.01 | ✅ 精确 |
| 0.125 | 0.001 | ✅ 精确 |
| 0.1 | 0.0001100110011... | ❌ 无限循环 |
故:3.14在内存中会多保存一点,一般为3.14000010
总结:所以浮点数无法精确保存,有可能少一点,有可能多一点
2:不能直接比较
既然无法精确保存,那么禁止使用浮点数进行比较!
#include <stdio.h> int main() { if (0.1 + 0.2 == 0.3) { printf("相等\n"); } else { printf("不相等\n"); } // 打印实际值 printf("0.1 + 0.2 = %.20f\n", 0.1 + 0.2); printf("0.3 = %.20f\n", 0.3); return 0; }解释:0.1、0.2、0.3 在二进制浮点数中都是无限循环小数,只能近似存储,所以 a + b 的近似值与 c 的近似值不完全相等
📌 [ 作者 ] shylyly
📃 [ 首次发布 ] 2024.3.6
❌ [ 最新修改 ] 2026.10.13
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教。