这是本人系列《底层的世界——计算机组成原理》的第二章——数的世界,用于记录分享和期末复习,欢迎讨论!
引言:在计算机的世界里,所有数据最终都以二进制的0和1存储。整数的表示相对简单,但当我们需要处理小数时,问题就变得复杂起来——计算机硬件本身并不认识”小数点”这个符号。为了解决小数的表示与运算问题,人类先后发明了定点数和浮点数两种方案。它们诞生于不同的时代背景,有着截然不同的设计哲学,至今仍在各自的领域发挥着重要作用。
计算机世界的”小数点”
小数,生活中非常常见。特别是我染上ds后深深的体会到了:今日吞5毛,明日吞3角,然后得一昔安寝——小数,对我这种穷逼来说,非常重要。
但是小数这个概念,根本就不存在于计算机的世界,为了表示数,秃头的老辈子们发明了定点数和浮点数。
一、定点数:小数点——约吗?
什么是定点数?(定点数的定义与背景)
只有整数怎么办?其中一种表示小数的方法就是把小数定下来。
定点数是计算机发展早期的产物。在20世纪中叶,计算机硬件极为简陋,运算单元只能处理整数运算,没有专门的浮点运算硬件。为了表示小数,工程师们想出了一个朴素的办法:人为约定小数点在二进制数位中的固定位置,这样就可以用整数运算来间接处理小数。
小数点:“约吗?”
例如:十进制的 78.125
整数部分:78使用二进制表示为:100 1110
小数部分:0.125使用二进制表示为:.001
所以合起来使用100 1110.001 表示十进制的78.125为什么不是78.91,因为91转2进制除不尽
神秘的表示方式:Q格式
拓展内容,初学者可以不看
定点数最常用的表示方法是Q格式(Q notation),其核心思想是将一个整数”放大”一定倍数来表示小数。
标准格式:Qm.n
注解:
- m:整数部分的位数(不包含符号位)
- n:小数部分的位数
- 总位数 = m + n + 1(额外1位是符号位)
例如,最常见的Q15格式(16位有符号数):
- 1位符号位 + 0位整数位 + 15位小数位
- 数值范围:-1 ≤ x < 0.9999695
- 精度:2⁻¹⁵ ≈ 0.0000305
残疾人转换公式:
- 浮点数 → 定点数:$Q = F × 2^n$ 其中Q为整数
- 定点数 → 浮点数:$F = Q × 2^{-n}$
举个例子,用Q15表示0.5:
0.5 × 2¹⁵ = 16384 转二进制为100 0000 0000 0000
| 符号位 | 数值 |
|---|---|
| 0 | 100 0000 0000 0000 |
定点数的优缺点
优点:
- 运算速度快:本质是整数运算,无需额外硬件支持
- 精度均匀:整个数值范围内精度保持一致,没有精度漂移
- 实现简单:硬件电路复杂度低,功耗小
- 结果确定:没有舍入误差的累积问题(在不溢出的前提下)
缺点:
- 数值范围小:同样位数下,能表示的数值范围远小于浮点数
- 32位Q16.15格式:最大值约为2¹⁶ = 65536
- 32位浮点数:最大值约为3.4×10³⁸
- 灵活性差:小数点位置一旦确定就不能更改,需要程序员手动定标
- 容易溢出:运算过程中如果数值超出范围,需要手动处理溢出
- 开发难度高:需要程序员自行管理小数点位置和缩放比例
这种方案的好处是硬件实现极其简单,只需要整数加法器和移位器就能完成所有运算。早期的单片机、DSP(数字信号处理器)大多采用定点架构,正是因为其成本低、功耗小、速度快。但是随着时间的推移,人们需要更精确的小数运算,这种情况下另一个魔丸要出世了……
浮点数:老牧师的科学计数法
魔丸出世
随着计算机应用向科学计算领域拓展,定点数的局限性越来越明显。物理学家需要表示电子质量(9×10⁻²⁸克),天文学家需要计算恒星距离(数以光年计),这些数值跨度极大,定点数根本无法覆盖。
为了解决”极大”和”极小”数的表示问题,科学家借鉴了十进制科学计数法的思想——用
$$
N = (-1)^S \times (1 + M) \times 2^{(E - \text{bias})}
$$
的形式表示数值,让小数点的位置随指数变化而”浮动”,这就是浮点数名称的由来。
先别吓晕,我会给你解释一下这一坨到底是什么的,我们先从十进制开始:
| 普通十进制数 | 科学计数法表示 | 说明 |
|---|---|---|
| 12300 | $1.23 \times 10^4$ | 小数点左移4位 |
| 0.00456 | $4.56 \times 10^{-3}$ | 小数点右移3位 |
| -789000 | $-7.89 \times 10^5$ | 负数保持符号在尾数前 |
| 9.8 | $9.8 \times 10^0$ | 1~10之间的数指数为0 |
可以看到十进制科学计数法的表示如下:
$$ N = a \times 10^n $$
- a:尾数,满足 $1 \le |a| < 10$
- n:指数,整数,代表小数点移动的位数
那么理解二进制科学计数法也就容易了:
$$
N = M \times 2^E
$$
- M:尾数
- E:指数,整数,代表小数点移动的位数
但是有个问题,在表示科学计数法的时候,我们规定尾数满足 $1 \le |a| < 10$,意思说不规定我们会有许多猎奇的表示方法,比如说用$0.0000001 \times 10^{1000000}$表示1,这你能受的了吗?
于是我们规定M必须是1.x开始,这个叫做浮点数的规格化,在公式中就能表示成
(1 + M)。
我们要能表示正负数,又引入了-1的指数S,控制S的值就能控制整个数的正负。
完全体如下:
$$
N = (-1)^S \times (1 + M) \times 2^E
$$
为了统一,1985年,IEEE(电气和电子工程师协会)发布了IEEE 754标准,统一了浮点数的表示格式,成为至今通用的工业标准。
IEEE 754标准
IEEE 754浮点数由三部分组成:符号位、指数位、尾数位。
通用公式:
$$
N = (-1)^S \times (1 + M) \times 2^{(E - \text{bias})}
$$
单精度浮点数(float,32位)
| 符号位(S) | 指数位(E) | 尾数位(M) |
|---|---|---|
| 1位(0=正,1=负) | 8位(偏移量为127) | 23位 (隐含整数部分的1) |
隐含整数部分:规格化的二进制浮点数,尾数的最高位总是1。因此这个1可以不用存储,节省了1位空间,实际精度比存储位数多1位。
数值范围:
- 最大值:≈ 3.4 × 10³⁸
- 最小值(绝对值):≈ 1.2 × 10⁻³⁸
双精度浮点数(double,64位)
| 组成部分 | 位数 | 位置 | 说明 |
|---|---|---|---|
| 符号位(S) | 1位 | 第63位 | 0=正,1=负 |
| 指数位(E) | 11位 | 第62-52位 | 偏移量为1023 |
| 尾数位(M) | 52位 | 第51-0位 | 隐含整数部分的1 |
隐含整数部分:规格化的二进制浮点数,尾数的最高位总是1。因此这个1可以不用存储,节省了1位空间,实际精度比存储位数多1位。
数值范围:
- 最大值:≈ 1.8 × 10³⁰⁸
- 最小值(绝对值):≈ 2.2 × 10⁻³⁰⁸
浮点数的优缺点
优点:
- 数值范围极大:同样位数下,范围远超定点数,适合科学计算
- 使用方便:程序员无需关心小数点位置,编译器自动处理
- 标准化程度高:IEEE 754是通用标准,跨平台一致性好
- 支持特殊值:可以表示无穷大、NaN(非数值)等特殊状态
缺点:
- 精度不均匀:数值越大,精度越低。靠近0的地方精度很高,远离0的地方精度稀疏这点要格外注意
- 运算速度慢:需要专门的浮点运算单元(FPU),硬件复杂度高
- 存在舍入误差:很多十进制小数(如0.1)无法用二进制精确表示
- 运算有损耗:多次运算后误差会累积,不能直接用”==”比较两个浮点数
对比
| 对比维度 | 定点数 | 浮点数 |
|---|---|---|
| 小数点位置 | 固定不变 | 随指数浮动 |
| 数值范围 | 小 | 极大 |
| 精度分布 | 均匀一致 | 近密远疏 |
| 运算速度 | 快(整数运算) | 慢(需FPU) |
| 硬件成本 | 低 | 高 |
| 开发难度 | 高(手动定标) | 低(自动处理) |
| 典型应用 | 嵌入式、DSP、音频处理 | 科学计算、图形渲染、通用编程 |
现实中的应用
定点数的主战场
- 嵌入式系统:MCU、单片机等资源受限的设备
- 数字信号处理:音频编解码、滤波、FFT等实时信号处理
- FPGA/ASIC设计:硬件电路中定点运算更省资源
- 工业控制:对确定性要求高,不允许误差累积
浮点数的天下
- 科学计算:物理模拟、气象预报、航天工程
- 计算机图形学:3D渲染、坐标变换、光照计算
- 通用软件开发:业务系统、数据分析、人工智能
- 金融计算(注意:高精度金融场景通常用十进制定点或BCD码)
总结
定点数和浮点数并非谁取代谁的关系,而是针对不同需求的两种设计取舍:
定点数是”空间换时间”的极致——用有限的数值范围换取更快的运算速度、更低的硬件成本和均匀的精度。它诞生于资源匮乏的年代,却在嵌入式、DSP等领域历久弥新。
浮点数是”时间换空间”的智慧——用更复杂的运算逻辑换取近乎无限的数值范围和使用上的便利性。它是科学计算的基石,也是现代通用计算机的标配。
理解这两种数值表示方式,不仅能帮我们写出更高效的代码,更能体会到计算机科学中”权衡”与”折中”的设计哲学——没有完美的方案,只有最适合场景的选择。
小思考:为什么0.1 + 0.2 在JavaScript和Python中不等于0.3?这正是浮点数二进制表示的经典问题,答案就藏在IEEE 754的尾数精度里。
这个问题同样也是数学中0.9无限循环等于1的原因。