3. 定点数基础运算实现
任务目标
在CEVA-X DSP上,浮点运算代价极高,基带算法移植的第一步是将浮点模型转换为定点实现。本案例完成一个最小可用的Q格式定点数基础运算库,支持加、减、乘、移位舍入与浮点互转,并给出验证方法。
Q格式约定
采用Qm.n格式,其中m为整数位(含符号位),n为小数位,总位宽16bit。本案例固定使用Q1.15格式:符号1位,整数0位,小数15位,数值范围[-1, 1)。定点数底层类型为int16_t,浮点转定点公式:fix = (int16_t)(flt * 32768.0f + (flt >= 0 ? 0.5f : -0.5f))。
核心实现
// q15_ops.h
#ifndef Q15_OPS_H
#define Q15_OPS_H
#include <stdint.h>
typedef int16_t q15_t;
#define Q15_ONE ((q15_t)0x7FFF) // 0.999969
#define Q15_HALF ((q15_t)0x4000) // 0.5
#define Q15_SCALE 32768.0f
// 浮点转Q15(四舍五入)
static inline q15_t q15_from_float(float x) {
if (x >= 1.0f) return Q15_ONE;
if (x < -1.0f) return (q15_t)0x8000; // -1.0
return (q15_t)(x * Q15_SCALE + (x >= 0 ? 0.5f : -0.5f));
}
// Q15转浮点
static inline float q15_to_float(q15_t x) {
return (float)x / Q15_SCALE;
}
// Q15加法(饱和)
static inline q15_t q15_add(q15_t a, q15_t b) {
int32_t sum = (int32_t)a + b;
if (sum > 0x7FFF) return Q15_ONE;
if (sum < -0x8000) return (q15_t)0x8000;
return (q15_t)sum;
}
// Q15减法(饱和)
static inline q15_t q15_sub(q15_t a, q15_t b) {
int32_t diff = (int32_t)a - b;
if (diff > 0x7FFF) return Q15_ONE;
if (diff < -0x8000) return (q15_t)0x8000;
return (q15_t)diff;
}
// Q15乘法:结果为Q30,右移15位并做饱和
static inline q15_t q15_mul(q15_t a, q15_t b) {
int32_t prod = ((int32_t)a * b) >> 15;
if (prod > 0x7FFF) return Q15_ONE;
if (prod < -0x8000) return (q15_t)0x8000;
return (q15_t)prod;
}
// Q15乘加:a*b + c(饱和)
static inline q15_t q15_mac(q15_t a, q15_t b, q15_t c) {
return q15_add(q15_mul(a, b), c);
}
// 算术右移n位(n: 1~15),带舍入
static inline q15_t q15_shr_round(q15_t x, int n) {
if (n <= 0) return x;
if (n > 15) n = 15;
int32_t rounding = (1 << (n - 1));
int32_t val = (int32_t)x + rounding;
return (q15_t)(val >> n);
}
#endif // Q15_OPS_H
验证代码
// test_q15.c
#include <stdio.h>
#include "q15_ops.h"
int main() {
// 测试1:浮点互转
q15_t a = q15_from_float(0.5f);
q15_t b = q15_from_float(-0.25f);
printf("0.5 -> %d (期望 16384)\n", a);
printf("-0.25-> %d (期望 -8192)\n", b);
// 测试2:加法 0.5 + 0.25 = 0.75
q15_t c = q15_add(a, q15_from_float(0.25f));
printf("0.5+0.25 = %f (期望 0.75)\n", q15_to_float(c));
// 测试3:乘法 0.5 * (-0.25) = -0.125
q15_t d = q15_mul(a, b);
printf("0.5*(-0.25) = %f (期望 -0.125)\n", q15_to_float(d));
// 测试4:饱和 0.9 + 0.9 应饱和到约0.99997
q15_t e = q15_add(q15_from_float(0.9f), q15_from_float(0.9f));
printf("0.9+0.9 = %f (期望饱和约0.99997)\n", q15_to_float(e));
// 测试5:移位舍入 0.75 >> 1 = 0.375
q15_t f = q15_shr_round(q15_from_float(0.75f), 1);
printf("0.75>>1 = %f (期望 0.375)\n", q15_to_float(f));
return 0;
}
编译与运行
# 宿主机验证(gcc)
gcc -Wall -O2 test_q15.c -o test_q15
./test_q15
# CEVA-X交叉编译(示例,实际路径以SDK为准)
# ceva-clang -mcpu=ceva-x -O2 test_q15.c -o test_q15.x
验证判定标准
- 测试1输出:
16384与-8192,证明浮点转定点正确; - 测试2输出浮点值在
0.7499~0.7501范围内(舍入误差±1 LSB); - 测试3输出浮点值在
-0.1251~-0.1249范围内; - 测试4输出接近
0.99997(饱和到Q15_ONE),证明饱和逻辑生效; - 测试5输出在
0.3749~0.3751范围内,证明带舍入右移正确。
以上5项全部满足即通过。在CEVA-X目标板上运行时,将printf替换为CEVA调试库输出接口即可,运算核心代码无需修改。