math/big 是处理超 int64 范围大数的唯一可靠方案,提供 *big.Int 等引用类型,需用方法链运算、字符串初始化、Cmp 比较、Text 转字符串,并注意内存复用与所有权管理。
大数超出 int64 范围时,math/big 是唯一可靠选择
go 原生整型最大只到 int64(约 9.2e18),一旦涉及阶乘、rsa 密钥、哈希碰撞模拟或区块链地址计算,立刻溢出静默失败——math/big 就是为此设计的。它不替换基础类型,而是提供 *big.int、*big.float 和 *big.rat 三类引用类型,所有运算都显式调用方法,避免隐式转换带来的歧义。
*big.Int 初始化和基本运算必须用方法链,不能用操作符
你不能写 a + b,也不能用 := 直接赋值大整数字面量。常见错误是试图这样初始化:var x *big.Int = 123456789012345678901234567890——这会编译失败,因为字面量仍是 int 或 int64。
- 从字符串初始化(最常用):
x := new(big.Int).SetString("123456789012345678901234567890", 10),返回(*big.Int, bool),务必检查第二个返回值 - 从 int64 初始化:
y := big.NewInt(123)(注意:仅支持int64,超限需用字符串) - 加减乘除都用方法:
z := new(big.Int).Add(x, y)、z.Mul(x, y);多数方法支持链式调用,如z.Add(x, y).Mul(z, z) - 原地修改更省内存:
x.Add(x, y)直接改x,而x.Add(y, z)则把结果存入x(即x = y + z)
比较、取模、位运算要注意方法签名和零值行为
*big.Int 没有 == 或 !=,比较必须用 Cmp():返回 -1/0/1,不是布尔值。取模 Mod() 对负数的处理与 Go 原生 % 不同——它始终返回非负余数(数学意义下的模),例如 new(big.Int).Mod(big.NewInt(-7), big.NewInt(3)) 得到 2,而非 -1。
- 判断是否为零:
x.Sign() == 0(Sign()返回-1/0/1),别用x.Cmp(big.NewInt(0)) == 0,虽等价但低效 - 位运算如
Lsh()(左移)、And()等,参数都是*big.Int,不能传整数;Lsh(x, n)中n是uint,不是*big.Int - 幂运算用
Exp(base, exp, mod):第三个参数为nil表示无模,但指数过大时仍可能耗尽内存,建议加长度校验
性能和序列化:避免频繁分配,优先用 Text() 而非 String()
*big.Int 是指针类型,每次 new(big.Int) 都分配堆内存。高频场景下(如循环内多次计算),应复用实例:var tmp big.Int,然后用 tmp.Add(...),而不是反复 new(big.Int).Add(...)。
- 转字符串推荐
x.Text(10),比x.String()快且稳定;Text(base)支持 2–36 进制,String()固定为十进制但内部做了额外格式处理 - JSON 序列化需自定义:标准库不支持直接 encode
*big.Int,通常转成字符串再封进 map;解码时先读字符串,再用SetString() - 二进制序列化用
x.FillBytes([]byte)最紧凑,但需提前分配足够长的切片,长度可由x.BitLen()推算
真正麻烦的从来不是“怎么算”,而是“谁 owns 这个 *big.Int”——传参、复用、清零(.SetInt64(0))的时机稍错,就可能引发隐蔽的数值污染或内存浪费。


















