fixedpoint库是专为嵌入式系统设计的零浮点依赖定点数运算实现,通过模板参数精确控制总位宽与小数位数(如fixed),本质为编译期确定的整数缩放抽象,所有运算在整数域完成,具备零运行时开销、确定性执行、自动类型提升与饱和转换等特性,彻底规避浮点精度不可控问题。

定点数类为什么不能直接用 float 或 double 替代
因为浮点数的精度不可控,尤其在金融计算、嵌入式控制或确定性仿真中,0.1 + 0.2 != 0.3 这类误差无法接受。定点数把小数位“固定”在整数内部:比如用 int32_t 存储,低 16 位当小数,高 16 位当整数——本质是整数运算,零误差、可预测、无 FPU 依赖。
但直接手写移位和截断容易出错:溢出没检查、舍入方式混乱、乘除后缩放错误。核心矛盾不是“能不能做”,而是“怎么让缩放逻辑不散落在业务代码里”。
FixedPoint 类的关键设计点:模板参数决定精度与范围
用模板参数控制小数位数,比运行时传参更安全高效。例如:FixedPoint 表示 16 位小数(即缩放因子为 1 ),底层用 <code>int32_t 或 int64_t 存储原始值。
- 小数位数
FracBits必须在编译期确定,否则无法生成正确的移位常量 - 底层类型选
int32_t还是int64_t取决于最大值需求:比如FixedPoint用int32_t最大支持 ±32767.999…;要更大范围就得换int64_t - 构造函数应显式接受整数或浮点数,并统一转换为缩放后的整数值,避免隐式转换歧义
示例构造逻辑:
template<int fracbits>
class FixedPoint {
static constexpr int64_t Scale = int64_t(1) (round(x * Scale))) {}
};</int>
乘除法必须手动缩放,且溢出检查不能省
两个定点数相乘后,结果缩放倍数变为 Scale²,必须右移 FracBits 恢复原缩放。但直接 a.raw_ * b.raw_ >> FracBits 会因中间结果溢出导致错误——int32_t 相乘最大可能到 2⁶²,远超 int32_t 范围。
- 乘法务必升位运算:用
int64_t接收乘积,再右移并截断回目标类型 - 除法同理:先左移保证精度,再整除,但要注意被除数左移后是否溢出
- 推荐在 debug 模式下加入
assert检查中间值是否在安全范围内,比如乘法前判断|a.raw_| > FracBits) - 舍入策略要明确:乘法常用 round-to-nearest(加
Scale/2再右移),除法建议 truncating(直接右移),避免负数行为不一致
隐式转换和运算符重载最容易引发静默错误
允许 FixedPoint 隐式转 double 看似方便,但会导致混合运算时精度丢失(比如 fp + 0.1 先把 0.1 转成定点,再运算,而 0.1 本身在 double 中就不精确)。更危险的是与整数混合:如果 operator+ 接受 int,却没定义 operator+=,就可能触发临时对象拷贝+析构开销。
- 禁止隐式转浮点:只提供
to_double()显式方法 - 所有二元运算符重载必须成对实现(
+和+=),且右侧参数类型要覆盖int、FixedPoint,避免模板推导失败 - 比较运算符(
==,)直接比 <code>raw_值,但注意:不同FracBits的模板实例之间不能比较,编译器应报错,别靠文档提醒
真正难的不是写完一个能跑的类,而是让缩放逻辑完全透明、不泄漏到上层业务里——一旦有人绕过类直接操作 raw_,整个定点语义就崩了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











