不能直接用 int64 计算 2^1000,因其值远超 int64 最大值 9223372036854775807;溢出静默发生且不可信,需用 big.Int 并注意初始化、原地运算、类型转换安全及并发安全。

为什么不能直接用 int64 算 2^1000?
因为 int64 最大只能表示 9223372036854775807,而 2^1000 是一个 302 位的数——它根本装不进任何固定长度整型。直接用循环乘会导致溢出后结果归零或翻转,比如 math.MaxInt64 + 1 会变成 -9223372036854775808,完全不可信。
- 溢出是静默发生的,Go 编译器不会报错,也不会 panic
- 一旦数值超出范围,后续所有计算都建立在错误基础上
- 哪怕只是临时中间值超限(比如幂运算中某次乘积),整个结果就废了
big.Int 初始化必须用方法,不能赋字面量
你写 var x *big.Int = 12345 是非法的;big.Int 是结构体,必须显式构造。常见初始化方式有三种,适用场景不同:
-
big.NewInt(n):仅适用于n是int64范围内的小整数(比如设置初始基数、指数) -
new(big.Int).SetString("12345678901234567890", 10):处理任意长度十进制字符串,返回(*big.Int, bool),务必检查ok -
new(big.Int).SetBytes([]byte{0x01, 0x02}):从大端字节流还原,常用于序列化/网络传输后重建
漏掉 .SetString 的第二个参数(进制)会默认按 0 解析,遇到 "0x1a" 这类前缀会失败——别依赖“看起来像十六进制”。
四则运算全是原地修改,链式调用要盯住接收者
Add、Sub、Mul 这些方法签名都是 func (z *Int) Add(x, y *Int) *Int,意思是:把 x + y 的结果写进 z,再返回 z。所以 a.Add(a, b) 是 a = a + b,a 原值被覆盖。
立即学习“go语言免费学习笔记(深入)”;
- 想保留原值?先用
c.Set(a)复制一份 - 写
new(big.Int).Add(a, b)没问题,但若在循环里反复 new,GC 压力陡增——应复用变量,如提前声明var tmp big.Int,每次用tmp.Add(...) - 链式调用如
r.Mul(&a, r.Sub(&b, &c))看似简洁,但前提是r不参与其他逻辑;否则中间结果污染难以调试
转字符串安全,转 int64 危险
输出最终结果几乎总是用 .String(),它无损、可读、无溢出风险。但反过来,用 .Int64() 强转时,如果大数实际值超出 int64 范围,结果是未定义的(可能截断、回绕,甚至因平台而异)。
- 需要判断是否可转?先调
z.BitLen() (正数)或z.Sign() == 0 || z.BitLen() (含零和负数) - 要取模或做位运算?别转成基础类型——
z.Mod(z, mod)或z.Bits()配合math/bits.OnesCount更直接 - 导出为字节切片用
z.Bytes(),注意它不带符号位;负数需先z.Abs(z)再取,符号单独判断
最易忽略的一点:所有 big.Int 方法都不并发安全。多个 goroutine 同时读写同一个 *big.Int 实例,必须加锁——它不是线程安全的容器,只是个普通 struct 指针。










