金融场景必须用decimal库,因float64基于IEEE 754二进制表示,无法精确存储0.1等十进制小数,导致舍入误差;math.Round或格式化仅影响显示,不改变底层值;decimal虽慢8–12倍但仍在微秒级。

Go语言原生浮点数类型无法可靠控制精度,金融/会计类场景必须用 decimal 库,否则必然出现舍入误差。
为什么 float64 在 money 场景下会出错
IEEE 754 双精度浮点数本质是二进制表示,而十进制小数(如 0.1、19.99)多数无法被精确表达。哪怕只做一次加法,结果也可能带微小偏差:
fmt.Printf("%.17f\n", 0.1+0.2) // 输出:0.30000000000000004
- 这种误差在累加、乘除、比较时会被放大,比如
if total == 100.0可能永远不成立 -
math.Round()或格式化(fmt.Sprintf("%.2f", x))只是显示截断,底层值未变,后续运算仍携带误差 - 银行系统要求“每笔交易可复现、零舍入偏差”,float64 天然不满足该前提
go-decimal 库实测性能表现
使用 github.com/shopspring/decimal 替代 float64 后,精度问题消失,但需关注其开销:
- 基础算术(
Add、Mul)比 float64 慢 8–12 倍,但仍在微秒级(单次 - 内存分配明显增加:每个
decimal.Decimal是 struct,含int64系数 +int32缩放因子,但避免了堆分配;频繁构造新实例(如循环中decimal.New(...))会触发 GC - 推荐复用:用
decimal.RequireDecimal预分配,或在 hot path 中缓存常用值(如decimal.New(100, 2)表示 100.00) - 注意
String()和RoundFloor()等方法会分配字符串或新 struct,高吞吐场景建议用Format配合预分配[]byte
常见误用导致精度“失效”
即使用了 decimal,下面这些操作仍会让精度控制前功尽弃:
- 从
float64构造:decimal.NewFromFloat(19.99)—— 此时 19.99 已是近似值,应改用字符串:decimal.NewFromStr("19.99") - 混用类型运算:
d.Add(float64Value)会隐式转成 float64 再转回,丢失精度;务必统一用decimal类型参与所有中间计算 - JSON 反序列化默认走
float64:需注册自定义UnmarshalJSON方法,或用json.RawMessage延后解析 - 数据库驱动返回
float64:如pgx默认将 numeric 列映射为float64,须显式配置pgtype.Numeric或启用pgx.QueryRow().Scan(&decimal.Decimal{})
真正影响落地的不是性能数字,而是所有输入源是否全程守住 decimal 类型边界——只要有一个 float64 悄悄溜进来,整个精度链就断了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











