浮点数的机器表示

官方信息技术老师·21 页·深入(追求细节与边界)·0 次浏览·2 天前
IEEE754二进制精度计算机组成

浮点数的机器表示

理清 IEEE 754 每个 bit 的作用,搞懂精度损失从何而来

按 空格/→ 演示下一步

1 / 21 页

全部页面点击任意一页,跳回舞台从这页播放

IEEE754二进制精度计算机组成

浮点数的机器表示

理清 IEEE 754 每个 bit 的作用,搞懂精度损失从何而来

1第 1 页 · 浮点数的机器表示

为什么需要浮点数

测量一片叶子的长度是 3.2 厘米,称一颗草莓重 12.5 克——这些带小数点的数字,整数表示不了。

整数的局限
只能精确表示整数,无法表达小数与极大/极小值
科学计数法思路
把数字拆成尾数与指数,用组合方式扩展范围
定点数的折中
固定小数点位置,范围与精度难以两全
浮点数的核心
让小数点随规模浮动,同时兼顾范围与精度
十进制科学计数法对应 →二进制浮点表示

尾数对应有效数字,指数对应数量级,基数从10换成2

N=±M×BEN = \pm M \times B^E
2第 2 页 · 为什么需要浮点数

浮点数的三要素

上一页我们看到,计算机不能像写数学那样随手点个小数点。要把一个小数塞进有限的二进制位里,必须把它拆成三件事:谁负责正负、谁负责量级、谁负责精度。

符号位
Sign 位,1 个 bit 即可,0 表正、1 表负,只管这一件事
指数位
Exponent 位,决定 2 的几次方,相当于移动小数点的位置
尾数位
Mantissa 位,存有效数字的位数,位数越多相邻数越分得开
科学计数法对应 →浮点数三要素

±M×B^E:正负号对应符号位,E 对应指数位,M 对应尾数位

(1)s×1.M×2Ebias(-1)^{s} \times 1.M \times 2^{E-bias}
3第 3 页 · 浮点数的三要素

浮点数存储结构

单精度32位与双精度64位的位布局图

浮点数存储结构
单精度32位与双精度64位的位布局图
4第 4 页 · 浮点数存储结构

IEEE 754 标准概述

上页看到 32 位切分成 1 位符号、8 位阶码、23 位尾数——但凭什么是这个切分?因为背后有一个统一江湖的标准。

标准诞生
1985 年 IEEE 颁布,统一浮点数格式与运算规则
规范内容
定义二进制表示、舍入规则、特殊值与异常处理
主流格式
单精度 32 位与双精度 64 位最为常用
特殊约定
±0、±∞、NaN 都有明确语义
全面采纳
CPU、GPU、编译器、编程语言都在遵循
各国电源插头标准对应 →IEEE 754 浮点标准

以前各厂商各做各的,统一后才能跨平台互通

5第 5 页 · IEEE 754 标准概述

浮点数编码四步骤

把一个十进制实数变成 IEEE 754 编码,要依次过四道关。

1
转二进制
整数部分除2取余,小数部分乘2取整
2
规格化
调成 1.xxx × 2^e 的标准形式
3
偏移指数
指数加偏移量,单精度+127
4
拼接尾数
符号位、指数、尾数按序拼成位串
6第 6 页 · 浮点数编码四步骤

编码实例:-6.5

手把手演示 -6.5 的完整编码过程

编码实例:-6.5
手把手演示 -6.5 的完整编码过程
7第 7 页 · 编码实例:-6.5

编码实例:0.1 的秘密

python

用 Python 揭开 0.1 的机器真相:为什么 0.1 + 0.2 不等于 0.3?

代码高亮加载中…

高亮行依次演示:① 0.1+0.2≠0.3 的现象;② 0.1 的真实存储位 0x3fb999999999999a;③ 解出符号/阶码/尾数三段;④ Decimal 还原十进制真值,证明 0.1 从来不是精确的 1/10。

8第 8 页 · 编码实例:0.1 的秘密

规格化表示

编码 -6.5 时我们写了 1.1011 × 2^2,为什么必须把整数部分强行凑成 1?这不是格式洁癖,而是 IEEE 754 用来"偷精度"的核心设计。

规格化要求
尾数必须写成 1.xxx…x 形式,整数部分恒为 1
隐含最高位
小数点前的这颗"1"必然存在,干脆不存
节省精度
省下的 1 bit 全部挪给小数部分,多一位有效数字
唯一表示
每个非零实数对应唯一规格化形式,避免移位歧义
十进制科学计数法 6.022×10²³对应 →二进制规格化 1.xxx×2ᵉ

都规定系数落在 [1, 进制) 区间,保证同一数只有一种标准写法

N=(1)s×1.M×2EbiasN = (-1)^s \times 1.\text{M} \times 2^{E - \text{bias}}
9第 9 页 · 规格化表示

非规格化表示

上一页讲了规格化数的最小值是 2^(-126)。那 0 到 2^(-126) 之间真的没有数?规格化在这里设了门槛——次正规数就是跨过门槛、填满这片空白的角色。

触发条件
阶码字段全为 0,且尾数 ≠ 0
隐含位归零
不再有「隐藏的 1」,尾数最高位按真实值 0 处理
指数补偿
实际指数 = 1 − bias 而非 0 − bias,保证平滑衔接
渐变下溢
在 0 与 2^(-126) 间补 2^23 个等距刻度,a − b 不再突然变 0
精度代价
越接近 0 有效位数越少;部分 CPU 运算明显变慢
体重秤在 0~1kg 间加密对应 →次正规数填补 0 到 2^(-126)

原本 1kg 以下无法读,加小刻度后 0.5kg 也能读;次正规数让 2^(-126) 以下的数也能区分

(1)s×0.M×21bias(-1)^s \times 0.M \times 2^{1-bias}
10第 10 页 · 非规格化表示

规格化与非规格化的边界

从0到规格化区,两个边界点紧邻,间距等于正常步长。

图解渲染中…
a3最大非规格化数 指数=0 隐含前导0a4最小规格化数 指数=Bias 隐含前导1
11第 11 页 · 规格化与非规格化的边界

精度与有效位数

上一页 0.1 算出来已经不再是 0.1,这背后就是「精度」在作怪。尾数 24 位听起来挺充裕,可它真能给你 24 位精确度吗?

24 位是二进制精度
尾数 24 位 = 二进制 24 位有效位,不是十进制
十进制只有约 7 位
换算:log₁₀2^24 ≈ 7.22,对应十进制有效位约 7
精度是相对的
相邻可表示数间隔随数值增大;1 附近约 10⁻⁷,10⁶ 附近约 0.1
24 格的尺子对应 →24 位尾数的浮点数

尺子刻度固定;量短物精度高、量长物精度低,有效位数取决于被测物大小

log10(224)7.22\log_{10}(2^{24}) \approx 7.22
12第 12 页 · 精度与有效位数

浮点数的表示范围

从正零出发向右:非规格化→规格化→有限最大→溢出无穷;左侧镜像。

图解渲染中…
F≈1.4×10⁻⁴⁵,最小正非规格化数H≈1.18×10⁻³⁸,最小正规格化数I≈3.4×10³⁸,最大正有限数J溢出或除零产生,指数字段全 1
13第 13 页 · 浮点数的表示范围

特殊值:无穷大与 NaN

学完表示范围,自然会追问:计算结果跑出范围怎么办?比如 1÷0、0÷0。IEEE 754 的解法很优雅——把『指数全 1』本该作废的编码,专门留给两个特殊值兜底。

±∞ 无穷大
指数全 1 且尾数全 0;符号位决定正负,是溢出方向的兜底值
NaN
指数全 1 且尾数至少一位为 1;标记『无定义结果』,二进制下有海量编码
触发场景
算术溢出→±∞;未定义运算(0/0、√负数、∞−∞、log 负数)→NaN
传播规则
NaN 有传染性,含 NaN 的运算结果仍是 NaN;∞ 仍可按规则运算
汽车『超速警告』vs『故障灯』对应 →±∞ vs NaN 的传播行为

超速时车还能开(∞ 仍可继续运算);故障灯一亮,后续读数都不可信——正如 NaN 污染一切

±: S11111111000NaN: S11111111(非全 0)\pm\infty:\ S\,11111111\,00\ldots0 \qquad \text{NaN}:\ S\,11111111\,(\text{非全 0})
14第 14 页 · 特殊值:无穷大与 NaN

舍入模式

上页编码 0.1 时,我们悄悄把无限二进制截到 23 位。但 IEEE 754 没简单说「五入」,而是给了 5 种模式。真实值恰在两个可表示数的正中间——选哪边?

五种舍入模式
IEEE 754 定义了 5 种不同方向的舍入
默认:最近偶数
Round to Nearest, Ties to Even,简称 RNTE
「偶数」指末位
舍入后尾数最低有效位 LSB 为 0
默认的理由
大量运算后统计偏差趋近于零
尺子量长度卡在两刻度正中对应 →最近偶数舍入

总偏一个刻度会系统偏差;选偶数刻度对称,长期平均误差趋零

x 恰为中点round(x)=a 若 LSB(a)=0, 否则 a+ulpx \text{ 恰为中点} \Rightarrow \text{round}(x) = a \text{ 若 } \text{LSB}(a)=0,\ \text{否则 } a+\text{ulp}
15第 15 页 · 舍入模式

float 与 double 的定义

c

用 <float.h> 的宏与 printf 格式说明符,把机器参数打印出来对照看。

代码高亮加载中…

FLT/DBL_EPSILON 把上一页"精度"量化成具体数字;f 后缀决定字面量类型;%e/%g 对应科学计数法与最短表示。

16第 16 页 · float 与 double 的定义

位级操作验证

c

用 union 让 float 与 uint32_t 共享同一段内存,直接看到 IEEE 754 的位模式。

代码高亮加载中…

union 让 float 与 uint32_t 共用 4 字节;写入浮点后用十六进制、二进制与位运算直接读出符号位、阶码、尾数字段,与 -6.5 的手工编码完全对得上。

17第 17 页 · 位级操作验证

整型与浮点型对比

一段 32 位内存,存整数和存小数走的路径天差地别——C/C++ 里那些隐式转换 bug,往往就藏在这。

整型 int
  • 补码定点存储,位与数值直接对应
  • CPU 原生指令最快,开销最小
  • 范围均匀离散,等步长
  • 精确无误差,整数一一对应
浮点 float
  • IEEE 754 分段:符号+指数+尾数
  • 需 FPU 协助,通常更慢
  • 极广但非均匀,近 0 处密集
  • 存在舍入误差,0.1 都存不准
计数、索引、位运算选整型;科学计算、工程测量选浮点。两类不可随便混——隐式转换是隐藏 bug 的温床。
18第 18 页 · 整型与浮点型对比

自测三题

验证对浮点数核心概念的理解

自测三题
验证对浮点数核心概念的理解
19第 19 页 · 自测三题

核心要点回顾

  • 浮点 = 符号+阶码+尾数 的离散编码,用精度换范围
  • IEEE 754 用规格化让精度均匀,非规格化平滑到 0
  • 0.1 不可精确:浮点运算是离散采样,非连续数学
  • 浮点 ≠ 实数:比较看差值、累加顺序敏感、溢出变 ±∞
  • 编码四步:符号→偏置阶码→隐含位+尾数→拼接
延伸主题:Kahan 求和:减少累加误差误差传播:ULP 与相对误差界定点数 vs 浮点数的取舍
20第 20 页 · 核心要点回顾

课后思考

先自己琢磨,再对照参考答案看思路对不对。

1为什么规格化表示要求尾数的小数点前必须是 1?

参考答案二进制规格化后小数点前必为 1,省下这一位可多 1 位精度——正是 IEEE 754 用 23+1=24 位精度的由来。

2为什么 0.1 + 0.2 在大多数语言里不等于 0.3?

参考答案0.1、0.2 的二进制是无限循环,只能用 52 位近似;两次近似相加,误差累积放大。这不是 bug,是设计取舍。

3如果把 double 的尾数从 52 位减到 10 位,会发生什么?

参考答案精度降到约 3-4 位十进制,但单位存储可存更大范围、计算更快。GPU 着色器、早期图形 API 就常这么做。

21第 21 页 · 课后思考