跳至内容
更新于 2026-11-03 · 阅读约 4 分钟

Q定点数C库

Q 定点滤波器共用 fl_q.h。C99,MIT 许可证。

Q 定点

格式转换、加减乘除和饱和运算均在头文件中,包含后即可调用。

fl_q.h 下载 fl_q.h
源码
/**
 * @file fl_q.h
 * @copyright SPDX-License-Identifier: MIT
 * @brief Shared signed Q arithmetic, C99, version 1.
 * @details Qm.f includes the sign bit in m; m + f = bits.
 *          Raw value r represents r / 2^f. Storage: 16 or 32 bits;
 *          fractional bits: 2..bits-1. int32_t carries either format.
 *          Round to nearest, ties away from zero; saturate at storage limits.
 *          No global format macros. Different formats may coexist.
 */
#ifndef FL_Q_H
#define FL_Q_H

#include <stdint.h>

/** @brief Signed storage width and fractional bit count. */
typedef struct
{
    uint8_t bits; /**< Storage width: 16 or 32. */
    uint8_t frac; /**< Fractional bits: 2..bits-1. */
} fl_q_format_t;

/**
 * @brief Test a format before using arithmetic functions.
 * @param[in] q Format to validate.
 * @return Nonzero for a supported format; zero otherwise.
 * @note All other functions require a valid format and raw operands in range.
 */
static inline int fl_q_valid(fl_q_format_t q)
{
    return (q.bits == 16u || q.bits == 32u) && q.frac >= 2u && q.frac < q.bits;
}

/**
 * @brief Saturate a wide raw result to a format's signed storage limits.
 * @param[in] value Wide raw result.
 * @param[in] q Valid destination format.
 * @return Saturated raw value.
 */
static inline int32_t fl_q_sat(int64_t value, fl_q_format_t q)
{
    const int64_t limit = INT64_C(1) << (q.bits - 1u);
    if (value >= limit)
    {
        return (int32_t)(limit - 1);
    }
    if (value < -limit)
    {
        return (int32_t)-limit;
    }
    return (int32_t)value;
}

/**
 * @brief Divide a wide integer, rounding nearest with ties away from zero.
 * @param[in] value Signed numerator, including INT64_MIN.
 * @param[in] divisor Positive denominator.
 * @return Rounded integer quotient.
 */
static inline int64_t fl_q_round_div(int64_t value, int64_t divisor)
{
    int64_t result = value / divisor;
    const int64_t remainder = value % divisor;
    const int64_t threshold = divisor / 2 + divisor % 2;
    if (remainder >= threshold)
    {
        ++result;
    }
    else if (remainder <= -threshold)
    {
        --result;
    }
    return result;
}

/**
 * @brief Multiply raw values; retain a wide result in the original Q units.
 * @param[in] a First raw operand.
 * @param[in] b Second raw operand in the same format.
 * @param[in] q Valid operand format.
 * @return Rounded product without storage saturation.
 * @note With frac >= 2, five such products fit an int64_t accumulator.
 */
static inline int64_t fl_q_mul_wide(int32_t a, int32_t b, fl_q_format_t q)
{
    return fl_q_round_div((int64_t)a * b, INT64_C(1) << q.frac);
}

/**
 * @brief Add two raw values with saturation.
 * @param[in] a First raw operand.
 * @param[in] b Second raw operand.
 * @param[in] q Valid operand format.
 * @return Saturated sum.
 */
static inline int32_t fl_q_add(int32_t a, int32_t b, fl_q_format_t q)
{
    return fl_q_sat((int64_t)a + b, q);
}

/**
 * @brief Subtract two raw values with saturation.
 * @param[in] a Raw minuend.
 * @param[in] b Raw subtrahend.
 * @param[in] q Valid operand format.
 * @return Saturated difference.
 */
static inline int32_t fl_q_sub(int32_t a, int32_t b, fl_q_format_t q)
{
    return fl_q_sat((int64_t)a - b, q);
}

/**
 * @brief Multiply two raw values with rounding and saturation.
 * @param[in] a First raw operand.
 * @param[in] b Second raw operand.
 * @param[in] q Valid operand format.
 * @return Saturated product in the same Q format.
 */
static inline int32_t fl_q_mul(int32_t a, int32_t b, fl_q_format_t q)
{
    return fl_q_sat(fl_q_mul_wide(a, b, q), q);
}

/**
 * @brief Divide two raw values with rounding and saturation.
 * @param[in] a Raw numerator.
 * @param[in] b Nonzero raw denominator in the same format.
 * @param[in] q Valid operand format.
 * @return Saturated quotient in the same Q format.
 */
static inline int32_t fl_q_div(int32_t a, int32_t b, fl_q_format_t q)
{
    int64_t numerator = (int64_t)a * (INT64_C(1) << q.frac);
    int64_t denominator = b;
    if (denominator < 0)
    {
        numerator = -numerator;
        denominator = -denominator;
    }
    return fl_q_sat(fl_q_round_div(numerator, denominator), q);
}

/**
 * @brief Convert an application value to raw Q, rounding and saturating.
 * @param[in] value Application value; NaN maps to zero, infinities saturate.
 * @param[in] q Valid destination format.
 * @return Raw Q value; the filter's sample loop needs no floating arithmetic.
 */
static inline int32_t fl_q_from_double(double value, fl_q_format_t q)
{
    const double scaled = value * (double)(INT64_C(1) << q.frac);
    const int64_t limit = INT64_C(1) << (q.bits - 1u);
    if (scaled != scaled)
    {
        return 0;
    }
    if (scaled >= (double)(limit - 1))
    {
        return (int32_t)(limit - 1);
    }
    if (scaled <= (double)-limit)
    {
        return (int32_t)-limit;
    }
    return fl_q_sat((int64_t)(scaled < 0 ? scaled - 0.5 : scaled + 0.5), q);
}

/**
 * @brief Convert a raw Q value to application units.
 * @param[in] raw Raw Q value.
 * @param[in] q Valid source format.
 * @return Value divided by 2^frac.
 */
static inline double fl_q_to_double(int32_t raw, fl_q_format_t q)
{
    return (double)raw / (double)(INT64_C(1) << q.frac);
}

#endif /* FL_Q_H */

Qm.fQm.f 的 mm 包含符号位,原始整数 rr 表示:

x=r2f,−2m−1≤x≤2m−1−2−f x=\frac{r}{2^f},\qquad -2^{m-1}\le x\le 2^{m-1}-2^{-f}
格式存储位宽小数位范围分辨率
Q2.141614[−2,  2−2−14][-2,\;2-2^{-14}]2−142^{-14}
Q4.283228[−8,  8−2−28][-8,\;8-2^{-28}]2−282^{-28}
Q16.163216[−32768,  32768−2−16][-32768,\;32768-2^{-16}]2−162^{-16}

fl_q_format_t 的两个字段为存储位宽和小数位,{32u, 28u} 对应 Q4.28。存储位宽可选 16 / 32,小数位范围为 2 至位宽减 1。

原始值以 int32_t 传递,宽结果使用 int64_t。运算采用最近舍入,半值远离零;超出存储范围时饱和。

接口

接口含义
fl_q_valid(q)检查格式参数
fl_q_from_double(x, q)数值转原始整数,舍入、饱和
fl_q_to_double(raw, q)原始整数转应用数值
fl_q_add(a, b, q) / fl_q_sub(a, b, q)饱和加减
fl_q_mul(a, b, q)舍入、饱和乘法
fl_q_div(a, b, q)舍入、饱和除法,b≠0b\ne0
fl_q_mul_wide(a, b, q)宽乘积,供累加后统一饱和
fl_q_sat(raw, q)宽原始值转存储范围
fl_q_round_div(raw, divisor)整数舍入除法,divisor > 0

加减乘除的两个操作数使用同一 Q 格式。浮点转换放在输入、输出处,滤波循环使用整数运算。

最小示例

下载 fl_q.h 和 q_demo.c 到同一目录:

q_demo.c 下载 q_demo.c
源码
/**
 * @file q_demo.c
 * @brief Multiply two application values using Q4.28 arithmetic, C99.
 * @copyright SPDX-License-Identifier: MIT
 */

#include "fl_q.h"
#include <inttypes.h>
#include <stdio.h>

/**
 * @brief Convert, multiply and print a raw Q value and its application value.
 * @return Zero after writing the result to standard output.
 */
int main(void)
{
    const fl_q_format_t q = {32u, 28u};
    const int32_t input = fl_q_from_double(0.5, q);
    const int32_t gain = fl_q_from_double(0.25, q);
    const int32_t output = fl_q_mul(input, gain, q);

    printf("%" PRId32 " %.17g\n", output, fl_q_to_double(output, q));
    return 0;
}
sh
cc -std=c99 -Wall -Wextra q_demo.c -o q_demo

运行 q_demo:

text
33554432 0.125

两列分别为原始整数和数值:0.5×0.25=0.1250.5\times0.25=0.125,0.125×228=335544320.125\times2^{28}=33554432。

接入滤波器

从 EMA / 滑动平均 或 二阶数字滤波器 导出 Q 定点代码,将三个文件放在同一目录:

text
fl_q.h
fl_filter.h
filter_demo.c
filter_demo.c 下载 filter_demo.c
源码
/**
 * @file filter_demo.c
 * @brief Run a step through a tool-exported Q filter, C99.
 * @copyright SPDX-License-Identifier: MIT
 * @details Place fl_filter.h and fl_q.h in the same include directory.
 */

#include "fl_filter.h"
#include <stdio.h>

/**
 * @brief Print sample index and filtered application value for a unit step.
 * @return Zero after processing 16 samples at the exported sampling rate.
 */
int main(void)
{
    Filter state;
    filter_reset(&state, 0);

    for (unsigned i = 0; i < 16u; ++i)
    {
        const double input = i < 4u ? 0.0 : 1.0;
        const FilterSample x = (FilterSample)fl_q_from_double(input, filter_q);
        const FilterSample y = filter_step(&state, x);

        printf("%u %.17g\n", i, fl_q_to_double(y, filter_q));
    }
    return 0;
}
sh
cc -std=c99 -Wall -Wextra filter_demo.c -o filter_demo

示例从 n=4n=4 加入单位阶跃,输出样本序号和滤波结果。

filter_q 是导出的 Q 格式,FilterSample 存放原始整数。每通道一个 Filter,按采样率调用 filter_step();filter_reset(&state, 0) 从零状态开始。

一份 fl_filter.h 对应一组滤波参数。公共头 fl_q.h 可供多个模块共用。