Go big.Int 运算后原变量为什么被改了
如果你发现 math/big.Int 做完 Add、Mul 后,之前保存的“大整数”也跟着变了,通常不是 Go 算错,而是两个规则叠在了一起:运算结果写入接收者,指针赋值又可能让多个变量指向同一个 Int。需要保留旧值时,用 Set 建立独立副本。
记住一句话:z.Add(x, y)的结果放进z;b := a只复制指针,new(big.Int).Set(a)才是复制数值。
本文只围绕接收者复用、别名和 Set/Copy 展开,示例中的结构图都是静态说明图,不是运行截图。
先看 Add 的 z、x、y 参数含义
big.Int 的方法采用“结果接收者”设计。官方文档把 Add 写成 func (z *Int) Add(x, y *Int) *Int,含义不是“返回一个全新整数”,而是把 x+y 写入 z,最后返回这个接收者本身。

package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(10)
b := big.NewInt(3)
var sum big.Int
// sum 是结果接收者,a 和 b 只是输入操作数。
sum.Add(a, b)
fmt.Println(a) // 10:输入 a 没有被 sum 覆盖
fmt.Println(sum) // 13:结果写入 sum
}
因此,下面两种写法的差别很关键:new(big.Int).Add(a, b) 会把结果放在新对象里;a.Add(a, b) 则明确要求把结果写回 a,这是受支持的接收者复用方式。
区分接收者复用和指针别名
真正容易误判的是普通赋值。big.Int 是结构体,但它的内部表示包含切片;官方文档明确不支持对 Int 做浅拷贝。把指针赋给另一个变量时,两个变量更直接地共享同一个对象:
package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(10)
b := a
// b 和 a 是同一个 *big.Int,Add 会把结果写回这个对象。
b.Add(b, big.NewInt(5))
fmt.Println(a) // 15:a 看到的是同一个对象的新值
fmt.Println(b) // 15:b 也指向这个对象
}
这里不是 Add 修改了某个“隐藏副本”,而是 a、b 根本没有两个独立的 Int。如果你只是想让两个名字代表同一个可变数值,这种写法没问题;如果要保存快照,就不能这样做。
用 Set 保留一个独立副本
Set 的用途就是把一个 Int 的当前数值复制到另一个 Int。接收者仍然是目标对象,但目标与源对象拥有独立的数值存储。

package main
import (
"fmt"
"math/big"
)
func main() {
a := big.NewInt(10)
snapshot := new(big.Int).Set(a)
// 只修改 a,snapshot 保留复制时的数值。
a.Add(a, big.NewInt(5))
fmt.Println(a) // 15
fmt.Println(snapshot) // 10
}
也可以先声明零值再调用 snapshot.Set(a)。不要用 *snapshot = *a 代替它;那是结构体层面的浅拷贝,可能共享内部表示,官方文档不把它当作安全复制方式。
循环累加时把结果变量单独拿出来
批量计算时,最稳妥的习惯是让输入值保持只读,把累加器作为唯一的结果接收者。这样既能复用内存,也不会让调用方持有的输入指针被意外改写。
package main
import (
"fmt"
"math/big"
)
func total(values []*big.Int) *big.Int {
result := new(big.Int)
for _, value := range values {
// result 是专用接收者,value 只作为输入,不在循环中被覆盖。
result.Add(result, value)
}
return result
}
func main() {
values := []*big.Int{big.NewInt(2), big.NewInt(4), big.NewInt(8)}
fmt.Println(total(values)) // 14
}
如果函数需要返回“计算前的值”和“计算后的值”,就先对原值调用 Set。如果函数的语义就是原地更新,则应该在命名和注释里明确这一点,让调用方知道传入的对象会变化。
使用 big.Int 时记住这几个边界
- 零值
var n big.Int可以直接参与大多数运算,不必先调用构造函数。 - 二元运算的第一个接收者是结果位置;需要保留输入时,使用另一个
Int或Set副本。 - 不要把
big.Int当成普通可安全复制的值类型;尤其避免结构体浅拷贝。 - 多个 goroutine 不能无保护地同时读写同一个
*big.Int;共享对象要由调用方同步,或为每次计算准备独立对象。 math/big适合任意精度整数运算,但官方文档提醒Int不适合作为密码学运算实现。
最后可以用一个小表快速判断:
| 写法 | 结果位置 | 是否保留旧值 |
|---|---|---|
sum.Add(a, b) | sum | 保留 a、b |
a.Add(a, b) | a | 不保留 a 的旧值 |
b := a | 同一个对象 | 不产生副本 |
new(big.Int).Set(a) | 新对象 | 保留复制时的值 |
常见问题
为什么 Add 还要返回 *Int?
返回接收者可以链式调用,也允许把结果直接传给后续函数;但它仍然是同一个接收者,不代表自动创建了新值。
Set 是不是只复制指针?
不是。Set 的语义是把源值复制到目标 Int;与 b := a 这种指针别名不同。
什么时候应该故意原地修改?
在循环累加、临时计算或明确的可变状态对象中,原地修改可以减少分配。只要调用方不再需要修改前的值,并且对象不会被并发访问,就可以把它作为结果接收者。
Go TLS 客户端没有 SNI 会怎样
- 上一篇
- Go TLS 客户端没有 SNI 会怎样
- 下一篇
- Go sort 排序结果不稳定是不是比较函数不满足约束
-
- Golang · Go问答 | 21分钟前 |
- Go bytes.Cut 怎样同时取分隔符两侧的字符串
- 180浏览 收藏
-
- Golang · Go问答 | 22分钟前 | Go 锁复制 sync.Mutex 指针接收者 copylocks
- Go sync.Mutex 为什么不能复制
- 194浏览 收藏
-
- Golang · Go问答 | 24分钟前 | 标准库 · ip地址 · IPv6 Go ipv4 net.IP IsUnspecified
- Go net.IP.IsUnspecified 怎样区分 0.0.0.0、:: 和普通地址
- 143浏览 收藏
-
- Golang · Go问答 | 32分钟前 |
- Go sort 排序结果不稳定是不是比较函数不满足约束
- 353浏览 收藏
-
- Golang · Go问答 | 51分钟前 | https · Go问答 · tls Go 证书校验 ServerName SNI ClientHello
- Go TLS 客户端没有 SNI 会怎样
- 146浏览 收藏
-
- Golang · Go问答 | 56分钟前 |
- Go hmac.Equal 能否直接比较不同长度摘要
- 434浏览 收藏
-
- Golang · Go问答 | 1小时前 | Go 调试信息 Reader.Next debug/dwarf DWARF
- Go debug/dwarf.Reader.Next 怎样遍历 DWARF 调试条目
- 342浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1022次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1025次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1163次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1131次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 968次使用
-
- Go 分数运算怎么避免中途转换成浮点数
- 2026-09-06 344浏览
-
- Go math/big 怎么精确计算高位数金额比例
- 2026-09-07 379浏览
-
- Go math/big 怎么计算超出 int64 的订单金额
- 2026-09-08 124浏览
-
- Go big.Float 比较结果不稳定时怎么设置精度和舍入
- 2026-09-08 295浏览
-
- Go math/big.Int.SetString 读取不同进制时怎么指定 base
- 2026-09-09 240浏览

