# fix_number **Repository Path**: Sevenfite/fix_number ## Basic Information - **Project Name**: fix_number - **Description**: 定点数计算库,支持四则运算,绝对值,取反,开根号计算,支持不同Q值混合使用 - **Primary Language**: C - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-08 - **Last Updated**: 2026-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fix_point — 32 位定点数运算库 单头文件、零依赖的 Q 格式定点数库。Q(小数位数)编码在变量类型中,编译期可知; 加/减在 WRAP 模式下为纯 32 位模运算,乘/除用 int64 保证精度,舍入全库统一为 ties→+inf。 ## 特性 - **Q 格式编码在类型中**:每个变量的 Q 值在定义时确定,编译期常量,运算时自动传入。 - **混合 Q 运算**:加减乘除和比较的两侧操作数可以是不同 Q 格式,自动对齐。 - **单次舍入**:所有运算全程只做一次舍入(到目标 Q),最小化误差累积。 - **三种溢出策略**(编译期选择): - **WRAP**(默认):32 位模运算回绕,最快。 - **SAT**:溢出钳位到 `INT32_MAX`/`INT32_MIN`,同 Q 加减用硬件溢出标志检测。 - **DEBUG**:int64 精确域 + `assert`,调试用。 - **编译期初始化**:`FIX_DEFINE_INT` / `FIX_DEFINE_FLOAT` 在编译期求值,带静态断言。 - **舍入统一**:四舍五入 ties→+inf,包括 FLOAT2FIX。 - **零依赖**:只需 ``(DEBUG 模式额外需 ``)。 ## 要求 - GCC 或 Clang 工具链(依赖 `typeof` GNU 扩展) - C99 以上(`-std=gnu99` 或更高) - `_Static_assert`:C11 或 GCC 4.6+ / Clang 自动检测;不支持时回退到 typedef 负数组法 ## 快速上手 ```c #include "fix_point.h" #include int main(void) { /* 定义变量:变量名, Q 格式 */ FIX_STRUCT_DEFINE(a, 16); /* Q16,未初始化 */ FIX_STRUCT_DEFINE(b, 16); /* 赋值 */ INT2FIX(12, a); /* 整数 12 -> Q16 */ FLOAT2FIX(3.5f, b); /* 浮点 3.5 -> Q16 */ /* 运算:result 的 Q 可以和操作数不同 */ FIX_STRUCT_DEFINE(sum, 8); /* 结果用 Q8 */ FIX_ADD(sum, a, b); /* 12 + 3.5 = 15.5,输出 Q8 */ printf("%f\n", FIX2FLOAT(sum)); /* 15.500000 */ /* 乘法 */ FIX_STRUCT_DEFINE(prod, 16); FIX_MUL(prod, a, b); /* 12 * 3.5 = 42,Q16 */ printf("%f\n", FIX2FLOAT(prod)); /* 除法 */ FIX_STRUCT_DEFINE(quot, 12); FIX_DIV(quot, a, b); /* 12 / 3.5 ≈ 3.4286,Q12 */ printf("%f\n", FIX2FLOAT(quot)); /* 比较:返回 -1/0/+1 */ if (FIX_CMP(a, b) > 0) printf("a > b\n"); return 0; } ``` 编译: ```sh gcc -std=gnu99 -Wall -Wextra -O2 main.c ``` ## 变量定义 ### 运行时定义(不初始化) ```c FIX_STRUCT_DEFINE(a, 16); /* 定义 Q16 变量,未初始化 */ ``` 带有编译期 Q 范围断言(Q 必须在 0..31)。 ### 克隆已有变量的 Q 格式 ```c FIX_DEFINE_CLONE(sum, a); /* sum 的 Q 和 a 相同 */ ``` ### 编译期初始化 ```c FIX_DEFINE_INT(pi, 16, 3); /* 整数 3 -> Q16,编译期求值 */ FIX_DEFINE_FLOAT(half, 16, 0.5f); /* 浮点 0.5 -> Q16,编译期求值 */ ``` 编译期版本带静态断言: - 所有宏检查 Q ∈ [0, 31] - `FIX_DEFINE_INT` 还检查整数值不溢出 Q 格式 ### const 版本 ```c FIX_DEFINE_INT_C(G_ONE, 16, 1); /* const Q16 = 1.0 */ FIX_DEFINE_FLOAT_C(G_PI, 16, 3.14f); /* const Q16 ≈ π */ ``` 可用于文件作用域(全局常量)、数组初始化等需要常量表达式的场景。 ### 编译期 raw 计算(独立使用) ```c uint32_t raw = FIX_RAW_INT(5, 16); /* = 5 << 16 */ uint32_t raw = FIX_RAW_FLOAT(-2.5f, 0); /* = -2 (ties→+inf) */ ``` 仅传常量,不要传有副作用的表达式(`FIX_RAW_FLOAT` 内部展开三次)。 ## 赋值与转换 ```c INT2FIX(12, a); /* 整数 -> 定点 */ FLOAT2FIX(3.5f, b); /* 浮点 -> 定点 */ FIX2FIX(target, src); /* Q 格式转换 */ float f = FIX2FLOAT(a); /* 定点 -> float */ int i = FIX2INT(a); /* 定点 -> int (floor 语义) */ ``` `FIX2INT` 是 floor 语义:`-8.5 → -9`(算术右移)。 ## 运算 所有运算支持混合 Q:操作数和结果可以是不同 Q 格式。 ```c FIX_ADD(result, a, b); /* result = a + b */ FIX_SUB(result, a, b); /* result = a - b */ FIX_MUL(result, a, b); /* result = a * b */ FIX_DIV(result, a, b); /* result = a / b */ FIX_NEG(result, a); /* result = -a */ FIX_ABS(result, a); /* result = |a| */ FIX_SQRT(result, a); /* result = floor(sqrt(a)) */ ``` `result` 的 Q 格式由其类型决定,可以与操作数不同。 ```c FIX_STRUCT_DEFINE(a, 16); FIX_STRUCT_DEFINE(b, 12); FIX_STRUCT_DEFINE(result, 8); /* a 是 Q16,b 是 Q12,result 是 Q8 */ FIX_ADD(result, a, b); /* 自动对齐到 Q16,相加,舍入到 Q8 */ ``` ## 比较 ```c int cmp = FIX_CMP(a, b); /* cmp < 0: a < b */ /* cmp = 0: a == b */ /* cmp > 0: a > b */ ``` 支持跨 Q 比较,自动对齐。 ## 溢出策略 通过编译选项选择,在 `#include "fix_point.h"` 前 `#define` 或编译行 `-D` 指定: | 选项 | 默认 | 说明 | |------|------|------| | `FIX_OVERFLOW_SAT` | 0 | 1 = 溢出钳位到 `INT32_MAX`/`INT32_MIN` | | `FIX_DEBUG_OVERFLOW` | 0 | 1 = 溢出/除零时 `assert` | | `FIX_USE_BUILTIN_OVERFLOW` | 自动 | 0/1 强制指定溢出检测方式 | 三种构建模式: ```sh # WRAP(默认):最快,溢出回绕 gcc -std=gnu99 -Wall -Wextra -O2 main.c # SAT:溢出钳位 gcc -std=gnu99 -Wall -Wextra -O2 -DFIX_OVERFLOW_SAT=1 main.c # DEBUG:溢出断言(调试用) gcc -std=gnu99 -Wall -Wextra -O2 -DFIX_DEBUG_OVERFLOW=1 main.c # SAT + DEBUG:断言后钳位(调试+发布同一代码) gcc -std=gnu99 -Wall -Wextra -O2 -DFIX_OVERFLOW_SAT=1 -DFIX_DEBUG_OVERFLOW=1 main.c ``` ### 各模式行为对比 | 场景 | WRAP | SAT | DEBUG | |------|------|-----|-------| | 加减溢出 | 回绕 | 钳位 | assert | | 除零 | UB | UB | assert | | float 越界 | UB | 钳位 | assert | | 速度 | 最快(32位) | 略慢(检测) | 不计 | > **注意**:除零在 WRAP 和 SAT 模式下是未定义行为,仅 DEBUG 模式有 `assert` 保护。 > 调用方需确保除数非零。 ## 内部设计 ### Q 格式编码 Q 值通过指针数组类型技巧编码在结构体中: ```c struct { union { char (*Q_Num)[qNum + 1]; uint32_t raw; }; } ``` `sizeof(*Q_Num) = qNum + 1`,通过 `FIX_Q(var)` 提取。`sizeof`/`typeof` 不求值, 零运行时开销。32 位目标结构体占 4 字节,64 位占 8 字节(指针大小)。 ### 舍入 全库统一:**四舍五入,ties→+inf**(`floor(x + 0.5)`)。 ``` 2.5 → 3 (向 +inf) -2.5 → -2 (向 +inf,不是 -3) 2.4 → 2 -2.6 → -3 ``` Q 格式转换用位检查法(`floor + 被舍弃的最高位`),避免 32 位中间加法回绕。 ### 执行域 - **WRAP 路径**(默认):加减/取负/绝对值/Q 转换是纯 32 位模运算。 32 位 `+half 再右移` 在中间值贴近 `INT32_MAX` 时会回绕出错,改用位检查法。 - **int64 路径**:乘/除/跨 Q 比较/SAT/DEBUG 恒用 int64。 乘积的高 32 位天然需要 64 位(如 Q16×Q16 乘积需 bit16..47)。 Cortex-M3+ 的 `SMULL`/`SDIV` 为单指令级。 - **SAT 同 Q 快路径**:`__builtin_add/sub_overflow` 映射硬件溢出标志(x86 OF / ARM V), 不支持时回退到符号位位运算法(纯 AND/OR/移位)。 ## API 速查 ### 变量定义 | 宏 | 说明 | |---|---| | `FIX_STRUCT_DEFINE(var, q)` | 定义 Q 格式变量(未初始化) | | `FIX_DEFINE_CLONE(new, old)` | 克隆已有变量的 Q 格式 | | `FIX_DEFINE_INT(var, q, val)` | 编译期整数初始化 | | `FIX_DEFINE_FLOAT(var, q, flt)` | 编译期浮点初始化 | | `FIX_DEFINE_INT_C(var, q, val)` | const 版整数初始化 | | `FIX_DEFINE_FLOAT_C(var, q, flt)` | const 版浮点初始化 | ### 赋值与转换 | 宏 | 说明 | |---|---| | `INT2FIX(val, x)` | 整数赋值(运行时) | | `FLOAT2FIX(val, x)` | 浮点赋值(运行时) | | `FIX2FIX(target, src)` | Q 格式转换 | | `FIX2FLOAT(x)` | 定点 → float | | `FIX2INT(x)` | 定点 → int(floor 语义) | ### 运算 | 宏 | 说明 | |---|---| | `FIX_ADD(res, a, b)` | 加法 | | `FIX_SUB(res, a, b)` | 减法 | | `FIX_MUL(res, a, b)` | 乘法 | | `FIX_DIV(res, a, b)` | 除法 | | `FIX_NEG(res, a)` | 取负 | | `FIX_ABS(res, a)` | 绝对值 | | `FIX_SQRT(res, a)` | 平方根(floor 语义) | | `FIX_CMP(a, b)` | 三态比较(-1/0/+1) | ### 内部访问 | 宏/函数 | 说明 | |---|---| | `FIX_Q(x)` | 提取变量的 Q 值(编译期常量) | | `FIX_RAW(x)` | 提取带符号 raw 值(`int32_t`) | | `FIX_RAW_INT(val, q)` | 编译期 int→raw | | `FIX_RAW_FLOAT(flt, q)` | 编译期 float→raw | ## 测试 测试文件 `fix_point.c` 包含用法示例和边界用例,覆盖混合 Q 运算、舍入、溢出策略。 ### 运行测试 ```sh # 默认(WRAP) gcc -std=gnu99 -Wall -Wextra -O2 fix_point.c -o fix_test && ./fix_test # SAT 模式 gcc -std=gnu99 -Wall -Wextra -O2 -DFIX_OVERFLOW_SAT=1 fix_point.c -o fix_test_sat && ./fix_test_sat # DEBUG 模式 gcc -std=gnu99 -Wall -Wextra -O2 -DFIX_DEBUG_OVERFLOW=1 fix_point.c -o fix_test_dbg && ./fix_test_dbg ``` ### 测试用例说明 | 用例 | 验证内容 | |------|---------| | `12(Q16) + 3.5(Q12) → Q8` | 混合 Q 加法 + 舍入 | | `3.5(Q12) - 12(Q16) → Q10` | 负数结果 | | `12(Q16) × 3.5(Q12) → Q8` | 混合 Q 乘法 | | `-8.5(Q10) × 3.5(Q12) → Q16` | 负数乘法 | | `12(Q16) ÷ 3.5(Q12) → Q8` | 无限小数舍入 | | `12(Q16) ÷ -8.5(Q10) → Q12` | 负数除法 | | `0.1f → Q16` | float 转换舍入(6553.6 → 6554) | | `INT32_MAX@Q16 → Q15` | 舍入边界(区分位检查法 vs +half 法) | | `NEG(12)` / `ABS(-8.5)` | 取负/绝对值 | | `CMP` 跨 Q | 同值不同 Q 等值、负数比较 | | `30000 + 20000` | 加法溢出(WRAP 回绕 / SAT 钳位) | | `INT2FIX(40000)` | 输入超范围 | | `NEG(INT32_MIN)` | 取负溢出 | 每个用例的 `printf` 输出标注了期望值(`expect ...`),可直接核对。