float内存占用究竟是多少?

99ANYc3cd6
预计阅读时长 15 分钟
位置: 首页 C语言 正文

IEEE 754 标准

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

c语言 float内存
(图片来源网络,侵删)

在几乎所有现代计算机系统(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,存储的指数范围是 0255
      • 当存储的指数为 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位 十进制数字的精度。

从内存到数值:一个完整的例子

我们来看一个具体的数字,5,它在内存中是如何存储的。

c语言 float内存
(图片来源网络,侵删)

步骤 1: 转换为二进制

  1. 整数部分 12:

    • 12 / 2 = 6 ... 余 0
    • 6 / 2 = 3 ... 余 0
    • 3 / 2 = 1 ... 余 1
    • 1 / 2 = 0 ... 余 1
    • 从下往上读,整数部分为 1100
  2. 小数部分 5:

    • 5 * 2 = 1.0 ... 整数部分为 1,小数部分变为 0
    • 小数部分为 .1
  3. 合并5 的二进制表示是 1

步骤 2: 规格化科学计数法

将二进制数表示为 xxxxx * 2^E 的形式。

c语言 float内存
(图片来源网络,侵删)
  • 1 = 1001 * 2^3

从这个形式中,我们可以直接得到三个关键信息:

  • 符号位5 是正数,所以符号位是 0
  • 实际指数E = 3
  • 尾数:有效数字部分是 1001,后面需要补足到 23 位,即 10010000000000000000000

步骤 3: 计算并填充指数位

  • 实际指数 = 3
  • 偏移量 = 127
  • 存储的指数 = 3 + 127 = 130
  • 130 转换为 8 位二进制:
    • 130 / 2 = 65 ... 余 0
    • 65 / 2 = 32 ... 余 1
    • 32 / 2 = 16 ... 余 0
    • 16 / 2 = 8 ... 余 0
    • 8 / 2 = 4 ... 余 0
    • 4 / 2 = 2 ... 余 0
    • 2 / 2 = 1 ... 余 0
    • 1 / 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.0sqrt(-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)、溢出、下溢等概念的基础。

-- 展开阅读全文 --
头像
织梦首页大图轮播模板
« 上一篇 昨天
c 语言里 const
下一篇 » 昨天

相关文章

取消
微信二维码
支付宝二维码

目录[+]