Go中float64不可用于金融计算,因其无法精确表示0.1等十进制小数,导致0.1+0.2≠0.3;应统一使用decimal包,通过NewFromString或New初始化,用Add/Sub/Mul/Div等方法运算,并确保数据库、JSON交互全程避免float64。

Go语言中float64不能用于金融计算——这不是配置问题,也不是写法不对,而是二进制浮点数根本无法精确表示0.1、0.01这类十进制小数,所有后续运算都在失真基础上进行。
为什么0.1 + 0.2 != 0.3是必然,不是偶然
因为0.1在float64里实际存储的是0.1000000000000000055511151231257827021181583404541015625,0.2同理。加法只是把两个近似值再近似一次,结果自然不是0.3。用fmt.Printf("%.30f", 0.1+0.2)就能看到真实值。
常见错误现象:
- JSON反序列化金额字段后直接赋给
float64,再参与计算 - 数据库查出
DECIMAL字段,ORM自动转成float64再塞进结构体 - 前端传
{"amount": 19.99},后端用json.Unmarshal解析到float64字段
decimal.NewFromFloat是最危险的初始化方式
它把一个已经失真的float64“固化”进decimal.Decimal,误差从此不可逆。例如decimal.NewFromFloat(0.1)得到的是0.10000000000000000555,不是0.1。
立即学习“go语言免费学习笔记(深入)”;
正确做法:
- 原始数据是字符串(如JSON、表单、DB读取)→ 用
decimal.NewFromString("0.1") - 原始数据是整数比例(如“分”)→ 用
decimal.New(1999, -2)(表示1999 × 10⁻²) - 上游只能给
float64?先fmt.Sprintf("%.2f", f)格式化为字符串,再喂给NewFromString
注意:decimal.RequireFromString会panic,生产环境必须用NewFromString并检查返回的err。
decimal.Decimal运算必须用方法,不能用操作符
decimal.Decimal是不可变类型,所有运算返回新实例。写a + b会编译失败;写a.Add(b)但不接收返回值,等于什么都没改。
关键规则:
- 加减:用
a.Add(b)、a.Sub(b),别写a += b - 乘除:用
a.Mul(b)、a.Div(b);需控制精度时,优先用a.MulRound(b, 2) - 比较:用
a.Cmp(b) == 0判断相等,a.Cmp(b) > 0表示a > b,==永远为false
性能提示:高频日志中避免反复调d.String(),它比fmt.Sprintf("%s", d)慢一个数量级;批量取整用d.Round(2),别绕路做d.Div(decimal.New(1, 0)).Round(2)。
数据库和JSON交互时最容易断层
代码层用了decimal.Decimal,但只要中间经过float64或默认JSON序列化,精度就丢了。
必须确认三处:
- GORM模型字段声明为
decimal.Decimal,不是float64或string;v2起需显式注册Scanner和Valuer接口 - PostgreSQL/MySQL的
DECIMAL字段,Go层必须直连decimal.Decimal,禁止ORM自动转成float64 - JSON输出默认是字符串(如
"19.99"),若前端坚持要数字类型,必须自定义json.Marshaler,且确保前端能处理任意精度字符串
最易被忽略的一点:哪怕你全程用decimal,只要有一次不小心把变量打印成fmt.Println(a)(触发String()),而这个a是从float64转来的,那你就已经在调试日志里埋下了精度污染的种子。


















