big.Int不能用运算符,必须调方法;零值为nil,未初始化调Add会panic;SetString不处理空格或前缀会静默失败。

big.Int 不能直接用 +、-、*、/ 运算符,所有计算必须调方法;零值是 nil,未初始化就调 Add 会 panic;字符串解析不指定进制或含空格会静默失败——这三个问题不处理好,代码要么编译不过,要么运行时崩溃,要么结果错得离谱。
为什么 new(big.Int).Add(a, b) 会 panic?
因为 var a *big.Int 声明后是 nil,而 Add 方法内部会访问 a.abs 字段(底层存储数字的切片),nil 指针解引用直接 panic。
常见错误写法:
var a, b, c *big.Int c.Add(a, b) // panic: nil pointer dereference
正确做法是显式初始化:
立即学习“go语言免费学习笔记(深入)”;
- 用
new(big.Int)分配内存并清零:a = new(big.Int) - 或用
big.NewInt(x)从 int64 初始化(但注意:字面量本身不能超 int64,比如big.NewInt(10000000000000000000)编译期就被截断) - 从字符串安全初始化必须用
SetString(s, base),且检查返回的ok布尔值
SetString 解析大数时为什么总出错?
SetString 默认按十进制解析,但对格式极其敏感:开头/结尾有空格、带 0x/0b 前缀、进制参数传错,都会导致解析失败且只返回 false,没有错误提示。
典型静默失败场景:
-
i.SetString(" 123 ", 10)→ 失败(首尾空格) -
i.SetString("0xff", 16)→ 失败(0x不被自动识别,得先strings.TrimSpace(strings.TrimPrefix(s, "0x"))) -
i.SetString("ff", 10)→ 成功但结果是 111(误当十进制)
安全写法:
a := new(big.Int)
s := strings.TrimSpace(" 0xff ")
s = strings.TrimPrefix(s, "0x")
_, ok := a.SetString(s, 16)
if !ok {
panic("invalid hex string: " + s)
}如何避免 Add/Mul 等方法意外覆盖原值?
big.Int 所有运算方法都是就地修改接收者,a.Add(a, b) 会把 a 改成 a+b,原 a 彻底丢失。这不是 bug,是设计——为了复用内存、避免频繁分配。
但容易踩坑的地方:
- 链式调用
a.Add(b, c).Mul(d, e)会编译失败(Mul是方法,不是函数,不能接在返回值后面) - 想保留
a又要算a+b,不能写c := a.Add(a, b)(a已被改) - 多个变量指向同一个
*big.Int实例时,一次Add可能同时污染多处逻辑
正确应对方式:
- 需要新结果?用
new(big.Int).Add(a, b)显式新建 - 需要复制?没有
Clone(),只能copy := new(big.Int).Set(a) - 高频循环中?复用一个实例,每次用
z.SetInt64(0)或z.Set(nil)重置
Div 和 Mod 为什么结果是负数?
big.Int.Div 和 big.Int.Mod 的行为和 Go 原生整数完全一致:向零截断。即 -7 / 3 == -2,-7 % 3 == -1。这不是 bug,是刻意设计的一致性。
但很多人真正想要的是数学上的非负模(比如哈希取桶、循环索引),这时:
- 别直接用
Mod,改用QuoRem一次性拿到商和余数 - 若余数为负,手动修正:
if r.Sign() == -1 { r.Add(r, m) }(m是模数) - 除数为 0 会直接 panic,调用前必须检查:
if m.Sign() == 0 { /* handle zero divisor */ }
性能提示:单独调 Div 再调 Mod,不如一次 QuoRem 高效,后者底层共享除法中间状态。
最麻烦的其实是“以为自己在用大数,其实中间某步悄悄掉回了 int64”——比如用 big.NewInt(9999999999999999999),字面量早被编译器截断;或者把 big.Int 转成 float64 再参与计算,精度瞬间归零。这些地方没日志、不报错,只默默给出错误答案。


















