定点数类不能直接用float或double,因其二进制浮点无法精确表示0.1、0.01等十进制小数,导致金融对账错误或游戏物理跨平台结果不一致;定点数通过“整数+隐含小数位”实现精确可控运算。

定点数类为什么不能直接用 float 或 double
金融计算要求精确到分(0.01 元),游戏物理有时需要确定性跨平台结果——而 float 和 double 是二进制浮点,无法精确表示 0.1、0.01 这类十进制小数。比如 0.1 + 0.2 == 0.3 在浮点下大概率是 false;金融系统里这种误差会滚成对账不平。
定点数本质是「整数 + 隐含小数位」:把 12.34 存成整数 1234,再约定小数点在倒数第 2 位。所有运算都在整数层面做,完全可控。
- 金融常用 2 位小数(
int64_t存元×100)或 4 位(存元×10000),避免除法截断 - 游戏物理常用 16:16 格式(32 位整数,高 16 位整数,低 16 位小数),兼顾范围和精度
- 别用
std::ratio或模板元编程搞“通用缩放因子”——编译期算得再漂亮,运行时除法/溢出检查还是得手写
核心接口怎么设计才不踩坑
一个可用的定点数类至少要覆盖构造、四则、比较、隐式/显式转换。关键不是功能多,而是边界行为明确。
- 构造函数必须显式接受缩放因子(如
fixed_point(1234, 2)表示 12.34),禁止从double隐式构造——否则用户传0.1进来,实际存的是二进制近似值 - 加减法直接整数运算,但要注意溢出:用
int64_t存 32 位定点数,加法前先检查__builtin_add_overflow(GCC/Clang)或std::add_overflow(C++23) - 乘法必须手动处理缩放:两个小数位为
N的数相乘,结果小数位是2N,要右移N位还原——这里容易忘截断或舍入,建议统一用round()而非截断 -
operator==直接比底层整数,但operator 必须考虑符号位扩展,负数右移时用算术移位(<code>>>在有符号数上是安全的)
金融场景下最常漏掉的三件事
金融不是“能算就行”,而是“每一笔都可追溯、可审计、可复现”。很多定点数类在这三点上翻车。
- 没有提供
to_string()精确输出:不能依赖std::to_string((double)value / scale),得手写整数转字符串 + 插入小数点,否则 100.00 可能输出成"100"或"100.0" - 除法没指定舍入模式:
divide_round_up、divide_truncate、divide_bankers得作为独立方法暴露,不能只留一个operator/——央行清算和交易所撮合规则不同 - 序列化没固化缩放因子:如果只存裸整数,换套系统(比如从 cents 改成 mills)读旧数据就全错。必须把
scale作为字段一起存,或用固定格式(如 ISO 20022 要求的ActiveOrHistoricCurrencyAndAmount)
游戏里用定点数要注意性能陷阱
游戏帧率敏感,但盲目优化反而坏事。定点数快,是因为整数指令流水线深、无异常开销——前提是别把它用成“慢速浮点模拟器”。
- 避免频繁缩放转换:不要在每帧里反复调用
to_float()做渲染坐标转换,改用批量预转换或 shader 端缩放 - 查表函数(sin/cos/sqrt)必须用整数输入:把角度映射到 0–65535,查 64K 大小的
int16_t表,别用float查表再转回定点 - 注意 CPU 架构差异:ARM Cortex-M4 有 SMLAL(带符号长乘累加),但 x86-64 的
imul对 64×64→128 位支持有限,大定点乘法得拆成 32 位块运算
真正难的从来不是“怎么实现加法”,而是“当需求从‘支持加减’变成‘符合 ISO 4217’或‘帧同步误差 fixed_point 类还敢不敢用”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











