云计算百科
云计算领域专业知识百科平台

浮点数的存储:IEEE‑754

目录

IEEE‑754 基础概念

为何规格化浮点数整数位只能是 1

二进制数字特点

float 23bit 尾数,为什么得到大约 7 位十进制有效数字

阶码偏移(移码)

特殊规则:阶码两种边界情况

尾数 M,隐藏的整数位 1

非规格化数(阶码全部 bit 为 0)

实际开发 / 做题坑点

二进制浮点数不能精确表达部分十进制小数

不要用 == 判断两个浮点数相等

十进制 0.1 → float 完整演算

IEEE‑754 特殊值

1. ±0 零

2. Inf 无穷大

3. NaN(Not a Number,非数)

小结特殊值

几个经典的浮点数存储程序可视化

总结


IEEE‑754 基础概念

我们平时写的float、double,在计算机内存中遵循 IEEE‑754 标准存储,不是简单把十进制小数转二进制直接存放。

32 位 float(单精度)内存 3 部分:

  • 符号位 S(1bit):0代表正数,1代表负数
  • 阶码 E(8bit):相当于指数,带偏移值,用来放大 / 缩小数字(规格化的二进制数是用科学计数法表示的—–>1.M*2^E——–>这只是逻辑式,实际存储不同)
  • 尾数 M(23bit):存放小数部分有效数字
  • 总位数:1+8+23 = 32bit

    64 位 double(双精度):

  • 符号位 S:1bit
  • 阶码 E:11bit
  • 尾数 M:52bit 总位数:1+11+52 =64bit
  • 关键知识点

  • 尾数部分默认隐藏整数位1。 例:二进制1.0101,只存小数点后面0101,开头的1不占用存储空间,节省一位。
  • 有效数字不是小数点后的位数,是全部有效数字
    • float:23 位尾数,换算十进制大约 6~7 位有效数字
    • double:52 位尾数,大约 15~16 位有效数字

    这就解释了你上一题:数值变大之后,float 尾部小数丢失精度。

  • ❗十进制小数,很多不能用二进制浮点数精确表达 例如0.1,内存里面是无限循环二进制小数,只能近似保存,存在微小误差。
  • c

    #include <cstdio>
    int main(void)
    {
    float f = 0.1f;
    if(f == 0.1)
    {
    printf("相等");
    }
    else
    {
    printf("不相等");
    }
    return 0;
    }

    这段代码运行输出:不相等,0.1 float 和 0.1 (double) 近似值不一样。


    思考题

  • float 一共 32bit,符号位占 1bit,阶码占多少 bit?尾数占多少 bit?
  • float 的十进制有效数字大约是多少位?double 呢?
  • 判断:0.1可以在 float 中被完全精确存储(对 / 错)
  • 1.8 23

    2.6~7

    3.错

    整数位为什么不是1就是0?为什么可以隐藏整数位置?

    批改

  • float 阶码8bit,尾数23bit ✔(符号位 1bit)
  • float:6‑7 位有效数字;double:15‑16 位 ✔
  • 错 ✔,0.1 是十进制小数,二进制下是无限循环小数,只能近似存储。

  • 为何规格化浮点数整数位只能是 1

    IEEE‑754 规格化浮点数,要求二进制科学计数法强制写成:1.M*2^ E

    二进制科学计数法,小数点左边只能是 1,不能是 0,也不能是 2。

    二进制数字特点

    二进制每一位只有两种取值:0、1。

    • 如果整数位写0:0.101 × 2^3,这个可以改写为 1.01 × 2^2

    把小数点向右挪一位,指数减 1,整数位就变成 1。

    👉规格化的定义:把数字调整成「整数部分严格等于 1」的形式。 所以规格化数整数位永远固定为1,不会出现 0、2 之类。

    既然永远固定是1,计算机就不需要把这个 1 存入内存,直接省略,只存小数点后面 M。 这就是那个隐藏位 1的来源,凭空多出来一位精度。


    那什么时候整数位可以是 0?

    只有非规格化数(阶码全部 bit 为 0)。 此时不再强制要求整数位为 1

    格式变成:0.M*2^{-126}

    用途:表示无限接近 0 的极小数字。

    简单总结:

  • ✅普通正常浮点数(规格化):整数位固定二进制1,隐藏,不占内存。
  • ✅阶码全 0(非规格化):整数位是0,不再隐藏。
  • 举个例子

    十进制 5.0

    十进制 5 →二进制 101.0

    科学计数法改写:1.010*2^2

    整数部分是1,内存只存小数点后010。

    有效数字是怎么算出来的?

    float 23bit 尾数,为什么得到大约 7 位十进制有效数字

    规格化 float 实际尾数:23bit 内存存储 M + 硬件隐藏的 1bit = 24bit 有效二进制位。

    问题转化:24 位二进制,等价多少位十进制有效数字?

    公式:

    位数(十进制) ≈ 二进制位数 × \\log_{10}2

    \\log_{10}2 ≈   0.3010

    代入: 24 *  0.3010   约等于   7.44

    👉所以 float 大约 6‑7 位十进制有效数字。

    同理 double: 尾数内存 52bit,加上隐藏 1bit,一共 53bit 二进制有效位 53 ×0.3010≈15.95

     👉double 约 15‑16 位十进制有效数字。


    通俗解释

    二进制每增加 1bit,能区分 2 倍大小; 十进制每增加 1 位,能区分 10 倍大小。

    log₁₀2 ≈ 0.3010含义:1 个二进制 bit ≈ 0.301 位十进制有效数字。

    • float:24bit ×0.301 ≈7.2 位;
    • double:53bit ×0.301≈16 位。

    ⚠️区分两件事:

  • 有效数字:可靠的数字位数,不是小数点后面多少位。 例:16777217.0f,整数 8 位,float 只有 7 位有效精度,最低位直接丢失。
  • printf("%.20f")打印很多小数,后面一大串是虚假的,只有前 6‑7 位可信。
  • 举实例

    c

    float x = 12345678.0f;
    printf("%f",x);

    真实数字:12345678 float 输出:12345678?实际会发生偏差。

    一共 8 位有效十进制数字,超出 float≈7.2 位极限,最低位不准。


    阶码偏移(移码)

    IEEE‑754 的阶码不直接存真实指数,要加上一个偏移量。

    • float(32 位):偏移量 = 127
    • double(64 位):偏移量 = 1023

    公式:

    存储的阶码 = 真实指数 + 偏移量 真实指数 = 存储阶码 − 偏移量

    举例:

    二进制科学计数 1.011 ×*2^3

    真实指数 =3;float 存入阶码字段的值 = 3+127=130。

    特殊规则:阶码两种边界情况

  • 阶码全部比特为 1
    • 尾数全 0:无穷大(inf)
    • 尾数非 0:非数字(NaN,无效计算结果,比如 0 除以 0)
  • 阶码全部比特为 0
    • 代表非规格化数,用来表示接近 0 的极小数字;此时不再隐藏开头的1。
  • 正常使用的普通浮点数,阶码既不全 0,也不全 1。

    c

    #include <cstdio>
    int main()
    {
    float a = 1.0f / 0.0f;
    printf("%f\\n", a); //输出 inf 无穷大
    return 0;
    }


    思考题

  • float 的阶码偏移量是多少?double 的偏移量?
  • 若 float 真实指数是 5,那么存入阶码字段的数值是多少?
  • float 阶码所有 bit 全为 1,尾数不为 0,表示什么?
  • 1.127 1023

    2.132

    3.无数字

    批改

  • float 偏移:127;double 偏移:1023 ✅正确
  • 5 + 127 = 130 ✅正确
  • 表述小问题:标准名称是 NaN(Not a Number,非数字),不是 “无数字”。代表无效运算结果,例如 0.0/0.0。
  • 小记忆:

    • 阶码全 1,尾数 = 0 →无穷大 inf
    • 阶码全 1,尾数≠0 → NaN

    尾数 M,隐藏的整数位 1

    IEEE‑754 规格化浮点数(日常绝大多数浮点数) 二进制科学计数法统一写成

    1.M*2^{E} (E为真实的E,除去偏移量)

    重点:开头这个整数 1,不存入内存,是隐藏位。 内存里面 23bit 尾数 M,只存小数点后面的部分。

    示例:

    二进制

     1.1010*2^E

    • 整数位1丢掉不存
    • 尾数 M 内存只保存:1010,后面补 0 填满 23bit。 读取数据的时候硬件自动把1补回来。

    收益:白白多出 1 位有效精度,这就是 float23bit 尾数却拥有接近 7 位十进制有效数字的原因。

    非规格化数(阶码全部 bit 为 0)

    当阶码全 0,不再使用隐藏的1,数字格式变为:

    0.M*2^{-126}

    用途:表示无限趋近 0 的极小数字,填补 0 附近的数值空隙。

    区分:

    • ✅正常数字:阶码不全 0 不全 1 →规格化数,隐藏1.M
    • ✅阶码全 0:非规格化数,没有隐藏的 1
    • ✅阶码全 1:特殊值 inf / NaN

    c

    #include <cstdio>
    int main(void)
    {
    float x = 1.0f / 3.0f;
    printf("%f\\n",x);
    return 0;
    }


    思考题

  • 规格化 float,尾数部分内存保存的是 1.M 还是小数点后的M?
  • float 为什么 23 位尾数,却能实现约 7 位有效十进制数字?
  • 什么时候浮点数不再隐藏整数位 1?
  • 1.(M)

    2.省略了整数位

    3.非规格化数

    批改

  • ✅正确:内存尾数只保存小数点后面的 M,不会存前面的 1。 逻辑:实际内存存 M;读取的时候硬件自动拼接成1.M。
  • 题目陷阱:内存里存的不是1.M,只存小数部分M。

  • ✅正确:省略隐藏整数位 1,凭空多出 1bit 有效精度。

  • ✅正确:阶码全 0,进入非规格化数,不再隐藏整数位 1。

  • 再复述一遍加深印象: 计算:

    1.101 * 2^4内存尾数 23bit,

    只存:101,前面的1不写入内存。


    实际开发 / 做题坑点

    二进制浮点数不能精确表达部分十进制小数

    十进制0.1,转二进制是无限循环小数。 但是 float 内存只有有限比特,只能截断保存近似值。 所以内存中0.1并不是真正等于 0.1。

    cpp

    #include <cstdio>
    #include <cmath>
    int main()
    {
    float a = 0.1f;
    if(a == 0.1)
    {
    printf("相等");
    }
    else
    {
    printf("不相等");
    }
    return 0;
    }

    输出结果:不相等

    0.1字面量默认是 double 类型;a是 float,两者近似值不一样。

    不要用 == 判断两个浮点数相等

    浮点数有微小误差,不能直接 if(x == y)。

    ✅正确写法:判断两者差值的绝对值小于一个极小允许误差

    cpp

    #include <cmath>
    // 允许误差 1e‑6
    if(fabs(a – b) < 1e‑6)
    {
    //视为两个浮点数近似相等
    }

    fabs():求浮点数绝对值;abs()用于整数。

    区分两个概念

  • 表示范围:float 可以存很大的数字;
  • 有效精度:数字位数一多,尾部小数不准。

  • 思考题

  • 为什么 0.1 不能在 float 中精确存储?
  • 浮点数比较相等,为什么不能直接用 ==?应该怎么写?
  • float、double 有效数字分别大约多少位?
  • 1.不能

    2.做差,其差小于一定范围

    3.6-7位 15-16位

    0.1具体在二进制是怎么存储的呢?

    批改

  • ✅正确。十进制0.1转二进制是无限循环小数,float 存储空间有限,只能保存近似值,无法精确存放。
  • ✅大体正确;完整表述:计算两者差值绝对值,fabs(a‑b) < 极小值(例如1e‑6),就认为近似相等。
  • 注意:浮点数求绝对值函数是fabs();整数用abs(),不要写混。

  • ✅正确:float:6‑7 位有效数字;double:15‑16 位有效数字。

  • 十进制 0.1 → float 完整演算

    十进制 \\(0.1_{10}\\)

    第一步:转二进制

    用乘 2 取整法:

    plaintext

    0.1 ×2 =0.2 取0
    0.2 ×2 =0.4 取0
    0.4 ×2 =0.8 取0
    0.8 ×2 =1.6 取1
    0.6 ×2 =1.2 取1
    0.2 ×2 =0.4 取0
    0.4 ×2 =0.8 取0
    0.8 ×2 =1.6 取1
    ……循环往复

    得到二进制:0.0001100110011……

    0011无限循环,永远写不完。

    第二步:写成规格化科学计数法(IEEE‑754)

    把小数点右移 4 位:

    1.100110011......* × 2^{-4}

    • 符号位:正数 → 0
    • 真实指数 E 真实 = -4
    • float 存储阶码 = 真实指数 +127 = -4+127 = 123

    第三步:尾数 M(内存只存小数点后面部分)

    逻辑数值:1.100110011 0011 0011……

    前面1.隐藏,内存尾数 23bit 只保存小数点之后:10011001100110011001101

    因为只能存 23 位,无限循环的比特直接截断,末尾做舍入。 👉所以内存里面存的,并不是真正的 0.1,是一个非常接近 0.1 的近似值。

    完整 float 内存 32bit 排布

    表格

    符号 (1bit)阶码 (8bit)尾数 M (23bit)
    0 01111011(123) 10011001100110011001101

    对应代码现象

    c

    float f = 0.1f;
    printf("%.20f",f);

    输出: 0.10000000149011611938

    可以看见,并不是严格等于 0.1。


    IEEE‑754 特殊值

    除普通规格化浮点数,IEEE‑754 定义了几组特殊数值,由阶码的边界状态决定。

    1. ±0 零

    阶码全部 bit 为 0,尾数 M 全部 bit 为 0。 符号位可以 0 或者 1,于是存在+0.0和‑0.0。 绝大多数场景下两者运算效果几乎一样。

    2. Inf 无穷大

    阶码全部 bit 为 1,尾数 M=0。

    • 符号位 0:正无穷 +inf
    • 符号位 1:负无穷 -inf 产生场景:正数除以 0,得到正无穷。

    3. NaN(Not a Number,非数)

    阶码全部 bit 为 1,尾数 M 不为 0。

    代表无效数学运算:0.0 / 0.0、sqrt(-1.0)负数开根号。

    ⭐NaN 最独特性质:NaN 不等于任何数字,甚至 NaN == NaN 的结果也是假。 不能用 x == NaN 判断是否是非数;C 语言用isnan(x)函数判断。

    c

    #include <cstdio>
    #include <cmath>
    int main(void)
    {
    float a = 0.0f / 0.0f;
    printf("%f\\n", a);
    if(a == a)
    {
    printf("a等于自己");
    }
    else
    {
    printf("a不等于自己");
    }
    return 0;
    }

    运行输出:

    plaintext

    nan
    a不等于自己


    思考题

  • float 阶码全 1,尾数 M 等于 0,代表什么?
  • NaN 有一个很特殊的判断特性是什么?
  • 什么运算会产生 NaN?举一个例子。
  • 1.无穷大

    2.NaN != NaN

    3.0/0

    批改

  • ✅无穷大(Inf),符号位决定是正无穷还是负无穷。
  • ✅NaN != NaN,NaN 连自己都不等于自己,不能用==判断 NaN。
  • ✅0.0 / 0.0会产生 NaN;补充:负数开平方sqrt(-1)也会得到 NaN。
  • 小提醒:必须是浮点数 0 相除;整数0/0程序直接崩溃,不是 NaN。


    小结特殊值

    表格

    条件结果
    阶码全 0,尾数全 0 ±0
    阶码全 1,尾数全 0 ±Inf 无穷大
    阶码全 1,尾数非 0 NaN 非数

    几个经典的浮点数存储程序可视化

    #include <cstdio>
    #include <cstdint>

    //打印float内存原始比特:符号|8阶码|23尾数
    void printFloatBits(float num)
    {
    uint32_t bits = *(uint32_t*)&num;

    printf("float = %.20f\\n", num);
    printf("32bit内存:");
    for (int i = 31; i >= 0; i–)
    {
    uint32_t mask = 1U << i;
    printf("%c", (bits & mask) ? '1' : '0');
    if (i == 31) printf(" "); //符号位分隔
    if (i == 23) printf(" "); //阶码、尾数位分隔
    }
    printf("\\n—————————————-\\n");
    }

    int main(void)
    {
    // 典型案例
    printFloatBits(0.1f); //案例1:经典无法精确存储,二进制无限循环
    printFloatBits(5.0f); //案例2:可以精确存储的正数 101.0 → 1.01 ×2^2
    printFloatBits(1.0f / 3.0f); //案例3:1/3无限循环小数
    printFloatBits(-2.0f); //案例4:负数,观察符号位变为1
    printFloatBits(0.0f); //案例5:正0,阶码全0,尾数全0
    //printFloatBits(1.0f / 0.0f); //案例6:正无穷Inf,阶码全部置1 了解,真正代码不可执行
    return 0;
    }


    总结

    核心回顾

  • 32 位 float
    • 符号位:1bit;阶码:8bit;尾数:23bit;偏移量:127
    • 规格化数:逻辑 \\(1.M×2^{E真实}\\);内存只存 M,隐藏整数1
    • 有效数字:6‑7 位十进制有效数字
  • 64 位 double
    • 符号位:1bit;阶码:11bit;尾数:52bit;偏移量:1023
    • 有效数字:15‑16 位十进制有效数字
  • 边界特殊情况
    • 阶码全 0:非规格化数,无隐藏 1,表示极小数字,可得到 ±0
    • 阶码全 1,尾数 0:无穷大 Inf
    • 阶码全 1,尾数≠0:NaN,非数,NaN 不等于自身
  • 工程做题铁律
    • 很多十进制小数(0.1)二进制无法精确存储,只能近似;
    • ❌严禁直接使用==比较浮点数;✅采用fabs(a‑b) < 1e‑6近似判断;
    • OJ 做题浮点计算优先选择double,减少精度丢失。

    c

    //浮点数近似相等模板
    #include <cmath>
    if(fabs(a – b) < 1e-6)
    {
    //视为相等
    }

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 浮点数的存储:IEEE‑754
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!