一、定点数基础:Q格式表示法、运算规则与溢出处理
各位同学好,我是老张。做DSP这行十几年了,今天咱们聊聊定点数。
你可能会问:现在浮点DSP芯片那么多,为什么还要学定点数?
原因很简单:成本、功耗、速度。定点芯片便宜、省电、跑得快。我当年做音频降噪算法,用浮点芯片成本压不下来,换成定点方案后,单颗成本降了60%。嗯,这就是现实。
1.1 Q格式:定点数的灵魂
定点数说白了,就是用整数来模拟小数。怎么模拟?靠的就是Q格式。
Q格式的写法是 Qm.n,其中:
- m 表示整数部分的位数(含符号位)
- n 表示小数部分的位数
- 总位数 = m + n
举个例子,Q1.15 格式:
- 总共16位(1位符号 + 15位小数)
- 能表示的范围:-1 到 0.999969
- 精度:1/32768 ≈ 0.0000305
核心公式:
实际值 = 整数值 × 2-n
整数值 = 实际值 × 2n
我个人习惯用 Q1.15 做音频处理,因为音频信号通常归一化到 [-1, 1] 之间,这个格式刚好够用。
1.2 常见Q格式速查表
| Q格式 | 总位数 | 范围 | 精度 | 典型应用 |
|---|---|---|---|---|
| Q1.15 | 16 | -1 ~ 0.99997 | 3.05e-5 | 音频、控制 |
| Q1.31 | 32 | -1 ~ 0.99999 | 4.66e-10 | 高精度滤波 |
| Q8.8 | 16 | -128 ~ 127.996 | 0.0039 | 传感器数据 |
| Q16.16 | 32 | -32768 ~ 32767.99 | 1.53e-5 | 通用计算 |
1.3 定点数加减法:对齐小数点
定点数加减法,最核心的一条:Q格式必须相同。
为什么?你想想看,两个不同精度的数直接加减,小数点没对齐,结果肯定错。
// 正确做法:Q1.15 + Q1.15
int16_t a = 0x4000; // 0.5
int16_t b = 0x2000; // 0.25
int16_t c = a + b; // 0x6000 = 0.75
// 错误做法:Q1.15 + Q8.8
// 必须先对齐格式再运算
我的经验:做加法前,先检查两个数的Q格式。我曾在项目中因为没对齐格式,导致滤波器输出全是噪声,排查了整整两天才找到问题。
1.4 定点数乘法:格式会变
乘法比加法麻烦一点。两个Q格式的数相乘,格式会发生变化:
Qm1.n1 × Qm2.n2 = Q(m1+m2).(n1+n2)
举个例子:
// Q1.15 × Q1.15 = Q2.30
int16_t a = 0x4000; // 0.5 (Q1.15)
int16_t b = 0x4000; // 0.5 (Q1.15)
int32_t c = (int32_t)a * b; // 0x10000000 = 0.25 (Q2.30)
// 通常我们会右移15位,转回Q1.15
int16_t d = (int16_t)(c >> 15); // 0x2000 = 0.25 (Q1.15)
这里要注意:乘法结果要用更宽的变量来存。我见过有人直接用16位存结果,数据直接溢出,调试时一脸懵。
1.5 定点数除法:精度与速度的权衡
除法是定点运算里最头疼的。为什么?因为除法会损失精度。
常用的做法是:先左移,再相除。
// Q1.15 除法:a / b
// 先将被除数左移15位,再除以除数
int16_t a = 0x4000; // 0.5
int16_t b = 0x2000; // 0.25
int32_t temp = ((int32_t)a << 15) / b;
int16_t c = (int16_t)temp; // 0x7FFF ≈ 2.0
避坑指南:我曾经在电机控制项目里直接用整数除法,结果转速控制精度不够,电机嗡嗡响。后来改成先左移再除法,问题就解决了。
1.6 溢出处理:该出手时就出手
定点数最怕什么?溢出。
两个正数相加变成负数,这叫上溢。两个负数相加变成正数,这叫下溢。
怎么处理?两种思路:
- 饱和保护:超出范围就钳位到最大值或最小值
- 绕回处理:让数据自然溢出(适合某些特殊场景)
// 饱和加法示例
int16_t sat_add(int16_t a, int16_t b) {
int32_t sum = (int32_t)a + b;
if (sum > 32767) return 32767;
if (sum < -32768) return -32768;
return (int16_t)sum;
}
// 饱和乘法示例
int16_t sat_mul(int16_t a, int16_t b) {
int32_t prod = (int32_t)a * b;
// 右移15位,并做饱和处理
prod = prod >> 15;
if (prod > 32767) return 32767;
if (prod < -32768) return -32768;
return (int16_t)prod;
}
我的建议:在音频处理中,一定要用饱和保护。否则一个溢出,扬声器里就是刺耳的爆音。我在做车载音响算法时,就因为漏了一个饱和处理,被客户投诉音质有问题。
1.7 知识体系总览
下面这张图,是我画的本章知识结构,方便你理解各个知识点之间的关系:
1.8 本章小结
好了,这一章的内容就这些。总结几个关键点:
- Q格式是定点数的根基,选对格式事半功倍
- 加减法要格式对齐,结果格式不变
- 乘法格式会变,要用宽变量存中间结果
- 除法先左移再除,避免精度损失
- 饱和保护是最后一道防线,千万别省
这些基础打牢了,后面讲滤波器实现、FFT优化的时候,你才能跟得上。嗯,今天就到这儿。
公众号:蓝海资料掘金营,微信deep3321