1. 概述 (Overview)
- 定义:定点数是一种在二进制计算机架构上表示数字的方法。它允许像浮点数(float/double)一样存储带有小数点的数值,但计算速度更快。
- 权衡:定点数的主要优势是计算时间更短(特别是在没有浮点运算单元FPU的硬件上),但代价是精度较低且灵活性不如浮点数。
- 本质:普通整数其实是定点数的一个特例,即小数点位于最低有效位(LSB)的右侧。例如,32位无符号整数可以看作是
fp(32, 0)。 - 编程支持:大多数编程语言和硬件本身不直接支持定点数数据类型(虽然有第三方库)。C++ 相比 C 语言的优势在于支持运算符重载,可以编写类库让定点数的加减乘除像原生类型一样直观。
2. 表示法 (Notation)
- 文章使用 Q记法(Q-format)来描述定点数:。
- :小数位(fractional bits)的数量。
- 示例: 代表一个32位的定点数,其中24位用于整数部分,8位用于小数部分。
3. 软件存储方式 (How We Store Them In Software)
- 定点数在软件底层是直接作为常规整数(signed 或 unsigned)存储的。
- 存储:内存中存储的整数值为
26624(二进制 0110 1000 0000 0000)。 - 高4位是整数部分(6 ->
0110),低12位是小数部分(0.5 -> 1000...)。
4. 范围与精度 (Range & Precision)
- 例: (8位) 范围是 -16 到 15.875。
- 完全由小数位数 决定,精度等于 (即最小可表示的非零值)。
5. 类型转换 (Converting To Fixed-Point)
- 代码:
rawVal = integer << numFracBits;
- 方法:将浮点数乘以缩放因子 (即
1 << f),然后强制转换为整数。 - 代码:
return (int32_t)(f * (double)(1 << q));
6. 定点数数学运算 (Mathematical Operations)
定点数运算本质上是整数运算,但在乘除法时需要处理缩放因子。
A. 加法与减法 (Adding/Subtracting)
- 不同精度:必须通过位移操作将其中一个数转换为与另一个数相同的精度(通常会有精度损失或溢出风险),然后再相加。
B. 乘法 (Multiplying)
- 结果位宽:两个定点数相乘,结果的整数位和小数位分别是两个操作数对应位数的和。
- 将操作数转换为更宽的类型(如两个
int32_t 相乘,先强转为 int64_t)。 - 代码示例:
int32_t res = ((int64_t)fp1 * fp2) >> 24;
C. 除法 (Division)
- 原理:类似于乘法,需要调整缩放因子。直接除法会丢失小数部分。
- 溢出注意:左移操作可能导致溢出,同样建议使用更大的整数类型(如
int64_t)作为中间存储。
D. 取模 (Modulus)
- 如果两个定点数的小数位数相同,直接对底层整数进行取模运算即可(
% 运算符)。 - 结果是正确的,例如
7.0 % 3.0 在定点数表示下直接运算会得到 1.0。
E. 倒数 (Inverse)
- 可以通过标准的定点除法实现:将数字 1 转换为定点格式(左移 位),然后再应用除法逻辑,或者直接理解为
(1 << (2*f)) / fp_val。
7. 浮点代码转换到定点代码的注意点
- 分析各环节所需最小精度,确定好精度要求并且做好精度控制
保持中间结果的高精度(使用更宽的数据类型)
只在最终输出时进行舍入
使用舍入而非截断减少误差