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调试库输出接口即可,运算核心代码无需修改。