go 的 math/big 中 *big.int 是指针类型,== 比较地址而非值,必须用 cmp() 比较;运算需调用 add() 等方法,不可用 + -;初始化、复制、复用行为不同,需警惕 nil、内存复用与进制解析陷阱。

Go 的 math/big 不是“升级版 int”,而是完全独立的、基于字符串/字节数组实现的任意精度类型;直接用 + - 等运算符会编译失败,必须调用方法。
为什么不能直接用 + 或 == 比较 *big.Int
因为 *big.Int 是指针类型,且内部结构包含动态分配的 []big.Word,值语义不适用。比如:
var a, b *big.Int = big.NewInt(10), big.NewInt(10) fmt.Println(a == b) // false —— 比较的是地址 fmt.Println(a.Int64() == b.Int64()) // true,但仅适用于能转成 int64 的数
- 所有算术操作都通过方法完成:
a.Add(a, b)(注意:Add是原地修改,返回a本身) - 比较必须用
a.Cmp(b):返回-1(a0(a==b)、1(a>b) -
==只能用于big.Int值类型(非指针)的浅比较,但几乎没人这么用——因为big.Int{}零值不等于big.NewInt(0)的底层表示
big.Int 初始化和赋值的常见陷阱
零值、复制、复用三者行为完全不同:
-
var x *big.Int→x是 nil,后续调用x.Add(...)panic -
x := big.NewInt(0)→ 安全,但注意它复用底层存储;多次调用如x.SetString("123", 10)会覆盖原值 - 想“拷贝”一个
*big.Int,必须用y := new(big.Int).Set(x),不能写y := x(只是指针复制) -
big.NewInt(n)只接受int64,超范围要用SetString("12345678901234567890", 10)
从字符串解析和输出时的进制与符号处理
SetString 和 Text 是最常出错的两个接口,尤其涉及负数和前导零:
-
x.SetString("-0xFF", 0)→ 自动识别前缀,支持0x/0o/0b;但0表示“自动推断”,不是十进制 -
x.SetString("0xFF", 10)→ 报错,因为10进制下F不合法 -
x.Text(16)输出小写十六进制,无前缀;要大写用strings.ToUpper(x.Text(16)) -
x.String()总是十进制,且对负数返回带-的字符串;但Text(10)行为相同,可互换
性能敏感场景下的内存复用技巧
频繁创建 *big.Int 会触发大量堆分配,尤其在循环中:
- 避免写
for i := 0; i - 改用预分配 +
SetInt64:var x = new(big.Int); for i := 0; i - 多个临时变量?用
new(big.Int)初始化一次,再用Set复用;big.Int本身不含 sync.Mutex,线程安全需自行保证 - 注意
Exp(幂运算)默认不复用结果空间,大指数时可能分配巨量内存;如有固定大小预期,可提前用z := new(big.Int).SetBit(nil, 0, 0)占位
真正麻烦的从来不是“怎么算”,而是“谁 owns 这块内存”和“这次比较到底比的是什么”。每次写 big.Int 操作前,先默念一遍:我是在改原值,还是需要新对象?这个字符串到底按几进制读?这个 nil 是不是还没 new?











