IEEE 754 标准
在深入探讨内存之前,必须知道一个关键点:C 语言本身并没有规定 float 在内存中的具体存储格式,它只规定了 float 必须提供至少 6 位十进制数字的精度,并且其值范围必须能够覆盖大部分实数。

在几乎所有现代计算机系统(x86, ARM, macOS, Linux, Windows)上,float 都遵循 IEEE 754 标准(全称是 IEEE 754 for Floating-Point Arithmetic),当我们讨论 float 的内存时,我们讨论的就是 IEEE 754 标准下的单精度浮点数。
内存布局:三部分结构
根据 IEEE 754 标准,一个 float 类型(单精度浮点数)在内存中占用 4 个字节(32位),并被划分为三个部分:
| 符号位 | 指数位 | 尾数位 |
|---|---|---|
| 1 bit | 8 bits | 23 bits |
让我们逐一解析这三部分的作用。
a) 符号位
- 位数:1 位
- 作用:表示浮点数的正负。
- 规则:
0:表示正数 ()1:表示负数 ()
- 示例:
+12.5的符号位是0-12.5的符号位是1
b) 指数位
- 位数:8 位
- 作用:表示浮点数的数量级,也就是小数点应该移动的位置,它决定了这个数有多大或多小。
- 规则:它存储的不是我们通常理解的整数,而是“移码”(或称“阶码”)。
- 移码 = 实际指数 + 一个固定的偏移量
- 对于
float,这个偏移量是 127。 - 存储的指数值 = 实际指数 + 127
- 8 位无符号整数的范围是 0 到 255,存储的指数范围是
0到255。- 当存储的指数为
0时,实际指数是0 - 127 = -127。 - 当存储的指数为
255时,实际指数是255 - 127 = 128。
- 当存储的指数为
- 特殊值:
- 如果指数位全为
0,表示这个数是非规格化数或零。 - 如果指数位全为
1,表示这个数是无穷大或非数字。
- 如果指数位全为
c) 尾数位
- 位数:23 位
- 作用:表示浮点数的精度,也就是有效数字部分。
- 规则:它存储的是一个小数部分。
- IEEE 754 标准规定,尾数部分的整数部分永远是
1(对于规格化数),这个隐含的 被称为“隐含位” (Implicit Bit) 或“隐藏位” (Hidden Bit)。 - 尾数部分的实际值是
尾数位存储的二进制值。 - 如果尾数位存储的是
..000(23个0),那么实际的尾数值就是1000...000(二进制)。 - 23位尾数可以提供大约 6到7位 十进制数字的精度。
- IEEE 754 标准规定,尾数部分的整数部分永远是
从内存到数值:一个完整的例子
我们来看一个具体的数字,5,它在内存中是如何存储的。

步骤 1: 转换为二进制
-
整数部分
12:12 / 2 = 6... 余06 / 2 = 3... 余03 / 2 = 1... 余11 / 2 = 0... 余1- 从下往上读,整数部分为
1100。
-
小数部分
5:5 * 2 = 1.0... 整数部分为1,小数部分变为0。- 小数部分为
.1。
-
合并:
5的二进制表示是1。
步骤 2: 规格化科学计数法
将二进制数表示为 xxxxx * 2^E 的形式。

1=1001 * 2^3
从这个形式中,我们可以直接得到三个关键信息:
- 符号位:
5是正数,所以符号位是0。 - 实际指数:
E = 3。 - 尾数:有效数字部分是
1001,后面需要补足到 23 位,即10010000000000000000000。
步骤 3: 计算并填充指数位
- 实际指数 =
3 - 偏移量 =
127 - 存储的指数 =
3 + 127 = 130 - 将
130转换为 8 位二进制:130 / 2 = 65... 余065 / 2 = 32... 余132 / 2 = 16... 余016 / 2 = 8... 余08 / 2 = 4... 余04 / 2 = 2... 余02 / 2 = 1... 余01 / 2 = 0... 余1- 从下往上读,指数位为
10000010。
步骤 4: 组合三部分
现在我们把三部分按顺序拼接起来:
| 符号位 | 指数位 | 尾数位 |
|---|---|---|
0 |
10000010 |
10010000000000000000000 |
float 类型的 5 在内存中的 32 位二进制表示就是:
0 10000010 10010000000000000000000
步骤 5: 转换为十六进制(便于查看)
将 32 位二进制每 4 位一组转换为十六进制:
0100 0001 0101 0000 0000 0000 0000 0000
4 1 5 0 0 0 0 0
5f 在内存中的十六进制表示是 0x41500000。
你可以用下面的 C 代码来验证:
#include <stdio.h>
#include <stdint.h> // 用于 uint32_t
void print_float_memory(float f) {
// 将 float 的内存地址强制转换为 uint32_t 的指针
uint32_t* ptr = (uint32_t*)&f;
printf("浮点数: %f\n", f);
printf("内存地址: %p\n", (void*)ptr);
// 打印 32 位二进制
printf("内存二进制: ");
for (int i = 31; i >= 0; i--) {
printf("%d", (*ptr >> i) & 1);
if (i == 31 || i == 23) {
printf(" "); // 分隔符
}
}
printf("\n");
// 打印 32 位十六进制
printf("内存十六进制: 0x%X\n", *ptr);
}
int main() {
float num = 12.5f;
print_float_memory(num);
float neg_num = -12.5f;
print_float_memory(neg_num);
return 0;
}
特殊值
IEEE 754 还定义了一些特殊的值,它们通过特定的指数和尾数组合来表示:
| 指数位 | 尾数位 | 含义 |
|---|---|---|
全为 0 |
全为 0 |
±0 (正零或负零) |
全为 0 |
不全为 0 |
非规格化数 (用于表示非常接近0的数,扩展了表示范围) |
全为 1 |
全为 0 |
(正无穷或负无穷,0 / 0.0) |
全为 1 |
不全为 0 |
NaN (Not a Number,0 / 0.0 或 sqrt(-1.0)) |
字节序问题
内存中的字节排列顺序与 CPU 架构有关,这被称为字节序。
- 大端序:高位字节存储在低地址。
0x41500000会按0x41,0x50,0x00,0x00的顺序存储。 - 小端序:低位字节存储在低地址。
0x41500000会按0x00,0x00,0x50,0x41的顺序存储。
x86 和 ARM 架构通常使用小端序,当你使用 printf("%X", *ptr) 这样的方式打印整个 uint32_t 的值时,你看到的是逻辑上的数值,而不是物理内存的字节顺序,字节序问题主要在你需要手动处理字节数据(例如从网络读取或写入文件)时才会变得重要。
| 特性 | 描述 |
|---|---|
| 大小 | 4 字节 (32 位) |
| 标准 | IEEE 754 单精度浮点数 |
| 结构 | 符号位 (1 bit) + 指数位 (8 bits) + 尾数位 (23 bits) |
| 符号位 | 0 为正,1 为负 |
| 指数位 | 存储 实际指数 + 127 (偏移量为 127),用于表示数量级 |
| 尾数位 | 存储 xxxx 的小数部分 xxxx,隐含了开头的 1,用于表示精度 |
| 精度 | 约 6-7 位十进制数字 |
| 范围 | 大约 ±3.4 x 10^38 到 ±1.2 x 10^-38 |
理解 float 的内存结构是理解浮点数运算精度问题(1 + 0.2 != 0.3)、溢出、下溢等概念的基础。
