登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go math/bits.Rem64 怎样对双字无符号整数取余

来源:17golang原创

时间:2026-10-28 18:18:31 417浏览 收藏

当一个无符号整数已经拆成高、低两个 uint64,但业务只需要它除以另一个 uint64 的余数时,可以直接使用 math/bits.Rem64。它把 hi 和 lo 看成一个双字被除数,返回余数;只要除数 y 不为零,即使完整商装不进 uint64,也不会因为商溢出而 panic。

官方地址:https://pkg.go.dev/math/bits#Rem64

Rem64(hi, lo, y) 的核心规则是:计算由 hi、lo 组成的双字无符号数对 y 取余;y == 0 仍会触发除零 panic,但不会像 Div64 那样因为商溢出而 panic。

三个核对点:hi 是高半字,lo 是低半字,y 必须是非零除数。不要把它误解成先把两个参数分别取余再拼接。

先把 hi 和 lo 还原成一个双字被除数

Rem64 的签名是 func Rem64(hi, lo, y uint64) uint64。其中前两个参数共同描述一个比单个 uint64 更宽的无符号被除数:hi 位于高半部分,lo 位于低半部分。概念上可以写成 (hi, lo),不需要在 Go 里先构造一个原生的 128 位整数。

package main

import (
	"fmt"
	"math/bits"
)

func main() {
	var hi uint64 = 1
	var lo uint64 = 5
	var y uint64 = 10

	// hi 与 lo 共同表示双字无符号被除数,不能把它们当成两个独立数。
	rem := bits.Rem64(hi, lo, y)
	// 这里展示返回的余数;实际值由双字被除数整体参与取余。
	fmt.Println(rem)
}

这个例子只展示接口边界,不依赖把双字被除数转成某个更窄的类型。若 hi == 0,它退化为普通的 lo % y 语义;当 hi != 0 时,低半字的结果不能脱离高半字单独推断。

Go math bits Rem64 的 hi 高半字、lo 低半字、y 除数和余数之间的静态结构说明图
图1:双字输入结构说明图,展示 hi、lo、y 与余数之间的静态关系;这是原创说明图,不是运行截图。

真正需要防的是 y 等于零

官方文档明确说明,Rem64 在 y == 0 时会因除零而 panic。这个边界与商是否溢出是两件事:取余结果总能落在 0 到 y-1 的范围内,但零除数没有合法余数。

package remsafe

import (
	"errors"
	"math/bits"
)

var ErrZeroDivisor = errors.New("rem64: divisor is zero")

func Remainder(hi, lo, y uint64) (uint64, error) {
	// 先拦截零除数,避免让 Rem64 在业务边界直接 panic。
	if y == 0 {
		return 0, ErrZeroDivisor
	}
	// 通过检查后,Rem64 只负责计算双字被除数的余数。
	return bits.Rem64(hi, lo, y), nil
}

如果参数来自协议、文件或用户请求,建议在调用点返回明确的 error。这样调用方可以把“除数缺失”记成参数错误,而不是用 recover 把正常的输入分支和程序缺陷混在一起。

Rem64 与 Div64 的分工不同

最容易混淆的是 Rem64 和 Div64 都接收 hi、lo、y。区别不在于它们是否使用双字被除数,而在于返回值目标不同:

调用结果商溢出时y == 0 时
Rem64只返回余数不因商溢出 panic除零 panic
Div64返回商和余数商无法放入 uint64 时有溢出边界除零 panic

因此,如果业务只需要做分桶、校验或周期计算,且不需要商,Rem64 比先调用 Div64 再丢弃商更贴合目标。反过来,如果还要拿到商,就必须接受 Div64 对结果范围的约束,不能因为只关心其中一个分支就忽略它。

Rem64 与 Div64 在余数、商溢出和零除数边界上的静态关系说明图
图2:Rem64 与 Div64 的结果边界说明图,展示两者共享双字输入但对商溢出的处理不同;这是原创说明图,不是运行证据。

把边界整理成调用前清单

接入实际代码前,可以按下面的顺序确认参数:

  1. 先检查 y:零值直接返回错误,不调用 Rem64。
  2. 再确认双字来源:明确哪个字段是高半字 hi,哪个字段是低半字 lo,不要交换顺序。
  3. 只需要余数就选 Rem64:不要为了取余承受 Div64 的商溢出约束。
  4. 确实需要商再选 Div64:并额外处理商能否放入 uint64 的条件。
  5. 覆盖边界:至少考虑 hi == 0、lo == 0、y == 1、y == 0 和高半字非零的输入。

一句话总结:Rem64 解决的是“双字无符号数只求余数”的问题;它不会替你处理零除数,但会把“商太大”从取余任务中隔离出去。

常见问题

Rem64 会先把 hi 和 lo 拼成 uint128 吗?

调用者不需要构造原生 uint128。接口直接用两个 uint64 表示双字被除数,并返回整体除以 y 的余数。

为什么 Rem64 不关心商溢出?

因为它只返回余数,余数本身受除数范围约束,可以装入 uint64。只有 y == 0 时,运算才失去定义。

可以用 Div64 取余再忽略商吗?

不建议。若业务只需要余数,直接使用 Rem64 能表达真实意图,并避开 Div64 的商溢出边界。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>