一、定点数基础: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 溢出处理:该出手时就出手

定点数最怕什么?溢出。

两个正数相加变成负数,这叫上溢。两个负数相加变成正数,这叫下溢

怎么处理?两种思路:

  1. 饱和保护:超出范围就钳位到最大值或最小值
  2. 绕回处理:让数据自然溢出(适合某些特殊场景)
// 饱和加法示例
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 知识体系总览

下面这张图,是我画的本章知识结构,方便你理解各个知识点之间的关系:

定点数基础:知识体系 定点数基础 Q格式表示法 加减法运算 乘除法运算 溢出与饱和 Qm.n 定义 范围与精度 格式选择原则 格式必须对齐 结果格式不变 格式会变化 需要宽变量 除法先左移 上溢与下溢 饱和保护 绕回处理 核心:格式对齐 → 宽位运算 → 饱和保护 三步走,定点运算不再愁

1.8 本章小结

好了,这一章的内容就这些。总结几个关键点:

  • Q格式是定点数的根基,选对格式事半功倍
  • 加减法要格式对齐,结果格式不变
  • 乘法格式会变,要用宽变量存中间结果
  • 除法先左移再除,避免精度损失
  • 饱和保护是最后一道防线,千万别省

这些基础打牢了,后面讲滤波器实现、FFT优化的时候,你才能跟得上。嗯,今天就到这儿。

公众号:蓝海资料掘金营,微信deep3321