3. 定点数设计与数值精度控制

在嵌入式部署中,浮点运算往往意味着更高的资源占用、更大的功耗,甚至在某些 MCU 或 FPGA 目标上根本不可用。定点数设计的目标,是在满足精度要求的前提下,用整数运算近似表达实数运算。MATLAB 的 Fixed-Point Designer 提供了 fi 对象作为定点建模的核心载体,它把“数值”和“格式”绑定在一起,使量化误差、溢出行为在算法开发阶段就可被观察和控制。

一、fi 对象与定点类型定义

fi 对象的基本构造形式为 fi(value, signed, wordLength, fractionLength)。其中 signed 取 1 或 0 表示有符号/无符号,wordLength 是总位宽,fractionLength 是小数位宽。定点格式通常写作 fixdt(signed, wordLength, fractionLength),例如 fixdt(1,16,12) 表示有符号 16 位、其中 12 位小数,其分辨率为 2-12 ≈ 0.000244,可表示范围约为 [-8, 8)。

需要区分两个关键量:分辨率由小数位宽决定,动态范围由整数位宽(wordLength − fractionLength)决定。二者此消彼长:增加小数位提升精度但压缩范围,减少小数位扩大范围但降低精度。设计定点格式,本质是在这两者之间做权衡。

a = fi(1.625, 1, 8, 4);      % 有符号8位,4位小数
disp(a.bin);                 % 二进制表示
disp(a.realmax);             % 最大可表示值
disp(a.realmin);             % 最小可表示值
disp(a.resolution);          % 分辨率 = 2^-4

T = fixdt(1, 16, 12);        % 定义定点类型
b = fi(0.3, T);              % 按类型量化
disp(b.double);              % 量化后的实际值

二、量化影响分析

把一个浮点值写入定点格式时,会发生两类误差:量化误差(舍入到最近可表示值)和溢出误差(超出范围时的饱和或回绕)。fi 默认采用“最近舍入、饱和溢出”,但可通过 fimath 修改。例如 fimath('RoundingMethod','Floor','OverflowAction','Wrap') 会改为向下取整和回绕。

量化误差的绝对上界是半个分辨率(最近舍入时)。若某信号分辨率 2-12,则单次量化误差不超过约 1.22e-4。但误差会在运算中累积:一次乘加链中,每个中间结果若都被量化,误差会叠加。因此定点设计不仅要看输入格式,还要看中间变量和累加器的格式。

溢出则更危险。饱和溢出把结果钳位到最大/最小值,误差大但可预测;回绕溢出会让大正数突然变成大负数,在控制回路中可能引发剧烈振荡。工程上通常对累加器保留足够整数位,对输出级采用饱和。

三、数值溢出与精度验证方法

验证定点设计是否合格,不能只看单个数值,而要看整段信号在定点下的表现。常用方法有三类:

  • 范围统计:用 min/max 或 fi 的 realmax/realmin 对比信号实际动态范围,判断整数位是否足够。
  • 误差度量:将定点结果与浮点参考结果相减,计算最大绝对误差、均方根误差(RMSE)和信噪比(SNR)。
  • 溢出检测:通过 fimath 的日志或 fi 的 OverflowAction 观察是否发生饱和/回绕,并统计发生次数。

下面给出一个可复用的验证脚本示例,用于评估某滤波器系数定点化后的误差:

% 浮点参考
x = sin(2*pi*0.05*(0:999));
b_ref = fir1(31, 0.2);
y_ref = filter(b_ref, 1, x);

% 定点系数与定点运算
T = fixdt(1, 16, 14);
b_fix = fi(b_ref, T);
y_fix = filter(b_fix, 1, x);

% 误差度量
err = double(y_fix) - y_ref;
maxAbsErr = max(abs(err));
rmse = sqrt(mean(err.^2));
snr = 20*log10(norm(y_ref)/norm(err));
fprintf('MaxErr=%.3e  RMSE=%.3e  SNR=%.2f dB\n', ...
        maxAbsErr, rmse, snr);

判断标准因应用而异:音频处理通常要求 SNR 大于 60 dB,控制回路更关注最大误差是否影响稳定性。若误差超标,可增加小数位、改用更高位宽,或对关键中间变量单独指定格式。

定点设计不是“位宽越大越好”。位宽增加会直接抬高乘法器、寄存器和功耗成本。合理做法是先按动态范围确定整数位,再按精度要求确定小数位,最后用误差度量验证。

四、定点格式设计流程

综合以上内容,一个可操作的定点设计流程如下:先采集或仿真得到信号的浮点动态范围,据此确定整数位宽;再根据可接受的量化误差确定小数位宽;然后为每个中间变量和累加器单独指定格式;最后用浮点参考对比,检查最大误差、RMSE、SNR 和溢出次数,不达标则回到格式调整。

定点数设计与精度验证流程 ① 动态范围分析 min/max 统计信号 ② 确定整数位宽 覆盖范围 + 余量 ③ 确定小数位宽 按误差要求定分辨率 ④ 定义 fi 格式 fixdt(1,W,F) ⑤ 中间变量与累加器格式 乘积/累加单独指定,防止中间溢出 ⑥ 精度与溢出验证 MaxErr / RMSE / SNR / 溢出次数 与浮点参考对比 达标? SNR/误差/溢出 不达标:调整位宽/格式 输出定点格式 用于代码生成 格式定义 中间变量 验证 判断

图中把“格式定义—中间变量—验证—判断”串成闭环:若验证不达标,回到格式调整;达标则输出可用于代码生成的定点格式。这样,定点设计就从“凭经验试”变成“有度量、可迭代”的工程过程。

在代码生成前,务必确认 fi 对象的 fimath 与目标处理器行为一致。例如某些 DSP 默认饱和溢出,而通用 C 代码默认回绕,若不一致会导致仿真通过但上板异常。

最后强调一点:定点精度控制不是一次性任务。算法结构变化、输入信号范围变化、滤波器阶数调整,都可能让原有格式不再适用。把范围统计和误差度量脚本保留下来,每次修改后重新运行,是保证定点设计长期可靠的低成本手段。