- 发布日期
Q15 定点数格式完全指南(附 DIFR 波束系数 0x7fff0000 实例解析)
作者
M先生
Q15 定点数格式完全指南(附 0x7fff0000 实例解析)
起因:调试雷达 DIFR 波束系数初始化代码时,遇到
m_DifrBeamData[j][k] = 0x7fff0000;这样的赋值,注释写着"零衰减零相位"。为什么是这个值?它和 Q15 有什么关系?本文从零开始把 Q15 讲透,最后逐位拆解这个例子。
一、为什么需要定点数?
1.1 浮点数的痛点
计算机里表示小数有两种方式:
- 浮点数(float/double):像科学计数法,动态范围大、精度高,但运算慢、硬件贵
- 定点数(fixed-point):用整数"假装"小数,运算快、硬件简单
雷达、通信、音频等实时信号处理系统,每秒要处理几百万次乘加运算。如果每个数都用浮点,芯片面积和功耗都扛不住。所以大量 DSP 芯片、FPGA 里的信号处理都用定点数——这就是 Q15 存在的根本原因。
1.2 一个直觉类比
想象一个"小数点位置固定"的刻度尺:
- 浮点数:小数点可以前后移动(3.14、0.0314、314)
- 定点数:小数点位置焊死在某个地方(比如 3.14 永远是 3.14,分辨率永远是 0.01)
Q15 就是:把小数点固定在"16 位有符号整数的最低位之前",也就是整数部分只有符号位(1 位),小数部分 15 位。
二、Q 格式表示法
Q 格式的通用写法是 Qm.n:
- m:整数位数(含符号位)
- n:小数位数
- 总位数 = m + n
常见格式:
| 格式 | 总位数 | 整数位 | 小数位 | 范围 |
|---|---|---|---|---|
| Q1.15(Q15) | 16 | 1(符号) | 15 | -1 ~ +0.99997 |
| Q1.31(Q31) | 32 | 1(符号) | 31 | -1 ~ +0.99999... |
| Q8.8 | 16 | 8 | 8 | -128 ~ +127.996 |
| Q16.16 | 32 | 16 | 16 | -32768 ~ +32767.99 |
Q15 的含义:16 位有符号定点数,1 位符号 + 15 位小数,能表示的范围是 。
注意:有些资料把 Q15 写作 Q.15 或 Q15,含义相同。还有无符号版本(如 UQ15),但 DSP 里绝大多数是带符号的 Q15。
三、Q15 的编码与解码
3.1 核心公式
编码(实数 → 整数):
解码(整数 → 实数):
3.2 最小分辨率(LSB)
Q15 能分辨的最小量:
也就是说,Q15 的精度约为小数点后 4~5 位,动态范围约 96dB(与 16 位 ADC 的理论动态范围一致)。
3.3 常见值对照表
| 十六进制 | 十进制整数 | 对应实数 | 含义 |
|---|---|---|---|
| 0x7FFF | 32767 | 0.99996948... | 最大正数 ≈ +1.0 |
| 0x7FFF0000(高16位) | 32767 | 0.99996948... | 增益 = 1.0(0dB) |
| 0x4000 | 16384 | 0.5 | 0.5 |
| 0x2000 | 8192 | 0.25 | 0.25 |
| 0x0001 | 1 | 0.0000305 | 最小正数(1 LSB) |
| 0x0000 | 0 | 0 | 0 |
| 0xFFFF | -1 | -0.0000305 | 最小负数 |
| 0x8000 | -32768 | -1.0 | 最小负数 = -1.0 |
关键记忆:0x7FFF ≈ +1.0(最大正),0x8000 = -1.0(最小负),0x0000 = 0。这就是"0x7fff 表示零衰减"的由来——增益系数取满刻度 1.0,不做衰减。
四、逐位拆解 0x7fff0000(本文核心实例)
回到您的代码:
// 设置DIFR波束系数和校正系数为零衰减零相位
memset(m_DifrCalibData, 0, sizeof(m_DifrCalibData));
for (j=0; j<MAXFREQDIVBEAM; j++)
{
for(k=0; k<g_ValidChannelNum; k++)
{
m_DifrBeamData[j][k] = 0x7fff0000;
m_DifrCalibData[j][k] = 0x7fff0000;
}
}
4.1 32 位拆成两个 16 位字段
0x7fff0000 是 32 位整数,按高 16 位 / 低 16 位拆开:
二进制:0111 1111 1111 1111 0000 0000 0000 0000
└──── 高16位 ────┘ └──── 低16位 ────┘
0x7FFF 0x0000
幅度/增益字段 相位字段
| 字段 | 值 | 解码含义 |
|---|---|---|
| 高 16 位(增益) | 0x7FFF = 32767 | Q15 格式 → 32767/32768 ≈ 1.0,即增益 = 1,零衰减(0dB) |
| 低 16 位(相位) | 0x0000 = 0 | 零相位偏移(0°) |
所以 0x7fff0000 = "幅度系数 1.0(不衰减)+ 相位 0(不移相)",正好对应注释"零衰减零相位"。
4.2 为什么这样打包?
在相控阵/波束形成系统中,每个通道的复加权系数(幅度 + 相位)需要存成一个数。常见的打包方式:
- 高 16 位存 Q15 幅度:0x7FFF = 1.0 满增益,0x4000 = 0.5(-6dB),0x0000 = 0(关断)
- 低 16 位存相位:通常相位也用 16 位无符号表示,, 对应接近 (或 )
这种"幅度高字、相位低字"的 32 位打包格式,在雷达波控、数字波束形成(DBF)、DIFR(数字中频接收)通道校正中是常见约定。
DIFR 说明:DIFR 通常是"Digital IF Receiver"(数字中频接收机)的缩写,负责多通道信号的数字下变频、滤波、幅相校正。波束系数(BeamData)和校正系数(CalibData)就是各通道的复加权系数。
4.3 为什么是 0x7fff 而不是 0x7fff + 符号扩展
注意 0x7fff0000 作为有符号 32 位整数看是正数(最高位 0),但如果把高 16 位 0x7fff 单独当作有符号 16 位整数看,也是最大正数 32767,语义一致。若想表示"增益 1.0"用 0x7fff 而不用 0x8000,是因为:
0x7FFF= +32767 ≈ +1.0(Q15 满刻度正)0x8000= -32768 = -1.0(符号位为 1,是负数)
增益不能为负,所以"零衰减"必须用正满刻度 0x7FFF,不能用 0x8000。
4.4 memset 与循环赋值的语义差异(工程细节)
代码里先 memset 清零整个数组,再用循环把有效槽位赋成 0x7fff0000。这意味着:
- 有效通道(
[j][k]在MAXFREQDIVBEAM × g_ValidChannelNum范围内):幅度 1.0、相位 0(直通) - 超出有效范围的槽位:保持 0(幅度 0 = 通道关闭)
两者语义不同:0 表示通道禁用/无效,0x7fff0000 表示有效通道零衰减直通。如果实际需求是"全部通道都直通",要确认循环范围覆盖了全部数组;如果数组更大而只初始化有效部分,超出部分会因 memset 而被禁用——这可能是有意设计(无效通道关闭),也可能是个隐患,值得确认。
五、Q15 的四则运算
5.1 加法/减法
直接整数加减,注意溢出:
int16_t a = 0x7FFF; // 0.99997
int16_t b = 0x0001; // 0.00003
int32_t sum = a + b; // 0x8000 → 溢出成了 -1.0!
0x7FFF + 0x0001 = 0x8000 = -1.0,正溢出变成最大负数。所以定点加法必须饱和(saturate):结果钳制到 :
int16_t sat_add(int16_t a, int16_t b) {
int32_t s = (int32_t)a + b;
if (s > 32767) return 32767; // 饱和到 +1.0
if (s < -32768) return -32768; // 饱和到 -1.0
return (int16_t)s;
}
5.2 乘法(重点!)
两个 Q15 数相乘,乘积是 Q30(2×15 位小数),需要 32 位变量承接,然后右移 15 位回到 Q15:
int16_t q15_mul(int16_t a, int16_t b) {
int32_t prod = ((int32_t)a * b) >> 15; // Q30 → Q15
return (int16_t)prod;
}
特殊情形: 用 0x8000 * 0x8000 = 0x40000000,右移 15 位得 0x8000(-1.0)——结果是正确的(-1×-1=1,但 Q15 里 +1.0 是 0x7FFF,0x8000 是 -1.0,所以这个特例严格说结果是 -1.0,标准做法是钳制到 0x7FFF)。
5.3 除法
Q15 除法先移位再除:
int16_t q15_div(int16_t a, int16_t b) {
int32_t q = ((int32_t)a << 15) / b; // 分子先扩到 Q30
return (int16_t)q;
}
5.4 乘加(MAC)—— DSP 的核心
滤波、FFT、波束形成都是大量 MAC:
每步乘加用 32 位(甚至 40 位)累加器,最后再一次性缩回 Q15,避免中间溢出:
int32_t acc = 0;
for (int i = 0; i < N; i++) {
acc += (int32_t)a[i] * x[i]; // 32位累加
}
int16_t y = (int16_t)(acc >> 15); // 最后缩放
六、为什么波束系数用 Q15 而不是浮点?
结合您的 DIFR 例子,雷达波束形成/通道校正系数用 Q15 有实际原因:
- 乘法效率:FPGA/DSP 的硬件乘法器对 16 位×16 位最友好(一个周期),32 位浮点乘法代价高
- 存储带宽:系数表可能很大(频率×通道×波束),16 位比 32 位浮点省一半存储和带宽
- 与 ADC/DAC 对齐:16 位 ADC 输出、16 位 DAC 输入都是 Q15 格式,全链路定点避免反复转换
- 精度足够:Q15 分辨率 (约 96dB 动态范围),对增益 0.01dB 量级的通道校正足够
代价:动态范围只有 96dB,且每级运算都会累积量化误差——所以 DSP 里常用"块浮点"(block floating point)或 Q31/Q40 累加来缓解。
七、工程注意事项清单
| 注意点 | 说明 |
|---|---|
| 溢出饱和 | 加减法结果超出 [-1,1) 必须饱和,否则符号翻转 |
| 乘法右移 | Q15×Q15 用 32 位积再 >>15,别丢符号位 |
| 舍入方式 | 右移前可加 0x4000(0.5 LSB)做四舍五入,减少截断误差 |
| 初始化约定 | 0x7FFF=直通(0dB)、0x8000=-1、0x0000=关断,需在代码注释中明确 |
| 有效范围 | 数组初始化要确认 memset 清零与循环赋值的覆盖范围一致 |
| 系数量化 | 浮点系数转 Q15 用 round(x*32768) 并检查 |x|<1 |
| 级联误差 | 多级滤波/多级校正的量化误差会累积,必要时用更高精度中间变量 |
八、Q15 速查卡
Q15 = 16位有符号定点数 = 1位符号 + 15位小数
范围: -1.0 ~ +0.99996948
LSB: 2^-15 ≈ 3.05e-5
编码: x_int = round(x × 32768)
解码: x = x_int / 32768
0x7FFF: +0.99997 (≈ +1.0, 满增益/零衰减)
0x4000: +0.5 (-6dB)
0x0000: 0 (关断)
0x8000: -1.0 (负满刻度)
加法: 饱和
乘法: 32位积 >> 15
MAC: 32位累加, 最后缩放
九、一句话总结
Q15 就是把"小数"用 16 位整数表示:乘以 32768 存进去,除以 32768 取出来。 而
0x7fff0000不过是一个 32 位"快递包裹"——高 16 位装着 Q15 格式的幅度 1.0(0x7FFF = 零衰减),低 16 位装着相位 0(0x0000 = 零相移),合起来就是雷达通道的"零衰减零相位直通系数"。
本文为技术学习笔记,实例代码来自实际工程中的 DIFR 波束系数初始化片段。
Q15 定点数格式完全指南(附 DIFR 波束系数 0x7fff0000 实例解析)
评论加载中…
