Q定点数C库
Q 定点滤波器共用 fl_q.h。C99,MIT 许可证。
Q 定点
格式转换、加减乘除和饱和运算均在头文件中,包含后即可调用。
源码
/**
* @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 */
的 包含符号位,原始整数 表示:
| 格式 | 存储位宽 | 小数位 | 范围 | 分辨率 |
|---|---|---|---|---|
| Q2.14 | 16 | 14 | ||
| Q4.28 | 32 | 28 | ||
| Q16.16 | 32 | 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) | 舍入、饱和除法, |
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 到同一目录:
源码
/**
* @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;
}
cc -std=c99 -Wall -Wextra q_demo.c -o q_demo运行 q_demo:
33554432 0.125两列分别为原始整数和数值:,。
接入滤波器
从 EMA / 滑动平均 或 二阶数字滤波器 导出 Q 定点代码,将三个文件放在同一目录:
fl_q.h
fl_filter.h
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;
}
cc -std=c99 -Wall -Wextra filter_demo.c -o filter_demo示例从 加入单位阶跃,输出样本序号和滤波结果。
filter_q 是导出的 Q 格式,FilterSample 存放原始整数。每通道一个 Filter,按采样率调用 filter_step();filter_reset(&state, 0) 从零状态开始。
一份 fl_filter.h 对应一组滤波参数。公共头 fl_q.h 可供多个模块共用。