Go语言中乘法精度取决于math/big对象初始化方式:big.Int.Mul输入须用SetString避免int64截断,big.Float.Mul精度由SetPrec(二进制位)决定且需字符串构造防浮点误差,高频运算应复用临时变量,MulRange适用于整数区间乘积,所有big类型非并发安全。

Go 语言里乘法本身没有“精度控制”一说——int 和 float64 的乘法是固定语义的,而真正能做任意精度乘法的只有 math/big;但它的精度不是靠“设置乘法参数”来调,而是靠你初始化时怎么建对象、怎么选类型、怎么传参。
big.Int.Mul 为什么结果看起来“不准”?
它其实非常准,问题往往出在输入上:
-
big.NewInt(99999999999999999999)这种写法已经错了——字面量99999999999999999999超过int64上限(9223372036854775807),编译期就被截断,传进去的是个完全不同的数 - 用
strconv.ParseInt(s, 10, 64)中转再喂给big.NewInt同样危险:解析失败会 panic 或返回 0,且依然卡在int64范围内 - 正确入口只有一条:
new(big.Int).SetString("99999999999999999999", 10),且必须检查返回的ok
big.Float.Mul 的精度由 SetPrec 决定,不是由乘法本身
big.Float 的乘法结果精度,完全取决于参与运算的两个操作数各自的 prec(二进制位数),而不是 Mul 方法能“提升”或“限制”什么:
-
z := new(big.Float).SetPrec(53).Mul(x, y)——z最多保留约 15–17 位十进制有效数字(因为log₁₀(2⁵³) ≈ 15.95) - 如果
x或y是用SetFloat64(0.1)构造的,那它们从诞生起就带了 IEEE 754 误差,Mul再准也救不回来 - 想真高精度?全部走字符串:
new(big.Float).SetPrec(256).SetString("0.1"),再乘 -
SetPrec(0)是未定义行为,运行时可能 panic 或给出随机值,永远不要留空
高频乘法别每次都 new,复用 tmp 变量能省 3–5 倍分配开销
循环里反复 new(big.Int) 创建临时对象,GC 压力大、性能差。更合理的模式是:
- 提前声明一个可复用的临时变量:
tmp := new(big.Int) - 每次计算前重置它:
tmp.SetInt64(0)或tmp.Set(nil)(后者清空底层字节数组) - 把
Mul目标指向它:tmp.Mul(a, b),而不是new(big.Int).Mul(a, b) - 注意:
tmp.Mul(a, b)会把结果写入tmp并覆盖旧值;如果后续还要用a或b,别误传tmp当作源操作数
big.Int.MulRange 比手写 for+Mul 快得多,尤其算阶乘
要算 n! 或一段连续整数乘积(比如 100 × 101 × … × 200),别写循环调 Mul:
-
result.MulRange(1, int64(n))是标准库内置优化实现,内部用分治策略减少中间结果大小和分配次数 - 它接受
int64范围的上下界,所以n不能超过math.MaxInt64;但你要算的是乘积值,不是索引,这通常够用 - 若上下界本身也是大数(比如来自字符串解析),得先用
big.Int算出边界值,再拆成多个MulRange段或改用Mul+Exp组合 - 别试图用
MulRange替代浮点幂运算——它不支持小数、不支持指数,只认整数区间
最常被忽略的一点:所有 big 类型都不是线程安全的。同一个 *big.Int 实例被多个 goroutine 同时 Mul 或 Add,结果不可预测,甚至直接 panic。并发场景下要么加锁,要么每个 goroutine 持有独立副本。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











