Go 大整数除法结果为什么没有小数
来源:17golang原创
时间:2026-11-03 03:00:39 384浏览 收藏
如果你用 math/big.Int 做除法,却发现 5 除以 2 得到的是 2,不是 2.5,通常不是精度丢失,而是类型语义已经决定了结果:big.Int 只能表示整数,(*big.Int).Quo 会执行和 Go 整数除法一致的截断。需要保留小数时,把两个操作数放进 big.Rat,再使用 Rat.Quo。
最小改法如下:整数商用 big.Int,精确分数用 big.Rat,最后是否显示成小数由格式化方法决定。

为什么 big.Int 除法没有小数
math/big 提供 Int、Rat 和 Float 三种任意精度数值类型。Int 表示有符号整数,所以它没有位置保存小数部分;调用 Quo 时,商会向零截断。也就是说,5/2 的整数商是 2,-5/2 的整数商是 -2。
package main
import (
"fmt"
"math/big"
)
func main() {
// Int 只能保存整数,Quo 的结果也只能是整数。
dividend := big.NewInt(5)
divisor := big.NewInt(2)
quotient := new(big.Int).Quo(dividend, divisor)
fmt.Println(quotient) // 2
// 负数同样向零截断,不是向负无穷取整。
negative := new(big.Int).Quo(big.NewInt(-5), divisor)
fmt.Println(negative) // -2
}
这里的关键不是变量是否“足够大”,而是接收结果的类型。big.Int 可以避免固定宽度整数溢出,却不会自动变成小数类型。若业务含义是“完成了多少个整份”,Quo 的结果就是正确的;若业务含义是“比例、单价或平均值”,就不能继续用 Int 承接商。
先用 QuoRem 判断小数被丢在哪里
如果暂时仍需要整数结果,可以用 QuoRem 同时拿到商和余数。余数不为零,说明原始除法不是整除;这时把商直接当成精确结果,才是问题的来源。
package main
import (
"fmt"
"math/big"
)
func main() {
// QuoRem 按 Go 的整数除法规则计算商和余数,商向零截断。
dividend := big.NewInt(5)
divisor := big.NewInt(2)
quotient := new(big.Int)
remainder := new(big.Int)
quotient.QuoRem(dividend, divisor, remainder)
fmt.Printf("商=%s,余数=%s\n", quotient.String(), remainder.String())
// 商=2,余数=1
}
QuoRem 适合分页、分组、拆包这类只关心“整份数量 + 剩余量”的场景。它不能让 quotient 产生小数;要表达完整的 5/2,应把两个整数转换成 Rat。
用 big.Rat 保存精确的除法结果
big.Rat 表示任意精度有理数,内部会保存分子和分母。可以先用 SetInt 把已有的 big.Int 转为有理数,再调用 Quo;也可以直接用 SetFrac 组装分子和分母。
package main
import (
"fmt"
"math/big"
)
func main() {
// 先把两个大整数复制为 Rat,再计算精确商。
dividend := big.NewInt(5)
divisor := big.NewInt(2)
left := new(big.Rat).SetInt(dividend)
right := new(big.Rat).SetInt(divisor)
result := new(big.Rat).Quo(left, right)
fmt.Println(result.RatString()) // 5/2
fmt.Println(result.FloatString(2)) // 2.50
}
如果输入本来就是两个可控范围内的 int64,也可以使用 big.NewRat(5, 2)。对于来自字符串或大整数的输入,优先使用 SetString、SetInt 或 SetFrac,避免先转成 float64 再丢失有效位。
按目标选择输出格式
计算结果和展示格式是两件事。RatString 适合日志、接口或需要保留精确值的场景;FloatString(prec) 适合固定小数位的展示;IsInt 适合判断当前有理数是否恰好是整数。

package main
import (
"fmt"
"math/big"
)
func main() {
// SetFrac64 会把 5/2 规范化为一个有理数,不会提前转为浮点数。
result := new(big.Rat).SetFrac64(5, 2)
fmt.Println(result.RatString()) // 5/2,精确分数
fmt.Println(result.FloatString(0)) // 3,按 0 位小数四舍五入
fmt.Println(result.FloatString(2)) // 2.50,固定两位小数
fmt.Println(result.IsInt()) // false,分母不是 1
}
FloatString 的参数是小数点后的位数,最后一位会按文档规则舍入;它只是把有理数格式化成字符串,并不会把 Rat 变成一个永久的 float64。如果下游需要继续做精确比较或金额计算,应继续保留 *big.Rat,不要只保存格式化后的文本。
几个容易踩到的边界
| 场景 | 建议 | 原因 |
|---|---|---|
| 只要整份数量 | big.Int.Quo | 结果语义就是整数商 |
| 还要知道剩余量 | big.Int.QuoRem | 同时得到商和余数 |
| 必须保留 5/2 | big.Rat.Quo | 保存精确有理数 |
| 展示固定小数位 | Rat.FloatString(n) | 只改变显示文本 |
第一,Int.Quo、Int.QuoRem、Rat.Quo 在除数为零时都会触发运行时 panic,外部输入应在调用前明确拒绝零值。第二,big.Int 和 big.Rat 的方法会把结果写入接收者,复用接收者前要确认不会覆盖仍要使用的输入。第三,负数场景要先确定业务是向零截断,还是需要欧几里得除法;Int.Div 与 DivMod 的规则和 Go 风格的 Quo 不同。
package main
import (
"errors"
"fmt"
"math/big"
)
func exactDivide(a, b *big.Int) (*big.Rat, error) {
// 先检查除数,避免 Rat.Quo 在零除数时 panic。
if b.Sign() == 0 {
return nil, errors.New("除数不能为零")
}
// SetInt 会复制输入值,结果对象随后独立承接 Quo 的输出。
left := new(big.Rat).SetInt(a)
right := new(big.Rat).SetInt(b)
return new(big.Rat).Quo(left, right), nil
}
func main() {
result, err := exactDivide(big.NewInt(5), big.NewInt(2))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.FloatString(3)) // 2.500
}
结论
Go 大整数除法没有小数,是因为 big.Int 的结果模型就是整数,Quo 会向零截断。排查时先看接收者类型,再用 QuoRem 判断是否存在余数;需要完整结果时改用 big.Rat,最后根据接口约定选择 RatString 或 FloatString。这样既能保留任意精度,也不会把展示格式误当成计算精度。
相关问题
big.Int.Quo 和 Div 有什么区别?
Quo 使用和 Go 一样的截断除法,Div 使用欧几里得除法。负数和余数处理不同,按业务定义选择。
big.Rat.FloatString 会不会改变原数?
不会。它返回格式化字符串,原来的 *big.Rat 仍然保持精确有理数。
什么时候应该用 big.Float?
当需求是可控精度的浮点运算、科学计数或近似值时再考虑 big.Float;如果分子和分母本身是整数且需要精确商,优先使用 big.Rat。
参考文档:https://pkg.go.dev/math/big
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
116 收藏
-
501 收藏
-
348 收藏
-
445 收藏
-
146 收藏
-
198 收藏
-
130 收藏
-
456 收藏
-
284 收藏
-
Golang · Go问答 | 1小时前 | web安全 · Go问答 · html/template · Go html/template template.HTML Go模板转义 HTML自动转义108 收藏
-
411 收藏
-
124 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习