math/big: add Int.Divide and RoundingMode aliases

Adds method Int.Divide to compute quotient and remainder of two Ints.
Adds RoundingMode aliases Trunc, Floor, Round and Ceil.

Fixes #76821

Change-Id: I7de80d76450f851d262b43a0685a0dc2218493f6
GitHub-Last-Rev: b52200bb94
GitHub-Pull-Request: golang/go#76820
Reviewed-on: https://go-review.googlesource.com/c/go/+/729860
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
TryBot-Bypass: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
This commit is contained in:
Armin Günther 2026-05-10 16:29:13 +00:00 committed by Robert Griesemer
parent 2677fe9bbe
commit 8f7f951965
4 changed files with 178 additions and 0 deletions

9
api/next/76821.txt Normal file
View file

@ -0,0 +1,9 @@
pkg math/big, method (*Int) Divide(*Int, *Int, *Int, RoundingMode) (*Int, *Int) #76821
pkg math/big, const Trunc = 2 #76821
pkg math/big, const Trunc RoundingMode #76821
pkg math/big, const Floor = 4 #76821
pkg math/big, const Floor RoundingMode #76821
pkg math/big, const Round = 0 #76821
pkg math/big, const Round RoundingMode #76821
pkg math/big, const Ceil = 5 #76821
pkg math/big, const Ceil RoundingMode #76821

View file

@ -0,0 +1,3 @@
<!-- go.dev/issue/76821 -->
[Int] now has method [Int.Divide] to compute quotient and remainder of two [Int] values.
It supports rounding modes [Trunc], [Floor], [Round] and [Ceil].

View file

@ -1308,3 +1308,85 @@ func (z *Int) Sqrt(x *Int) *Int {
z.abs = z.abs.sqrt(nil, x.abs)
return z
}
// Rounding modes that determine how the integer quotient is adjusted in an integer division.
// See Daan Leijen, “Division and Modulus for Computer Scientists”, for details.
const (
Trunc = ToZero // T-division (same as Go division)
Floor = ToNegativeInf // F-division
Round = ToNearestEven // R-division
Ceil = ToPositiveInf // C-division
)
// Divide computes the integer quotient q and remainder r such that
//
// q = f(x/y)
// r = x - y*q
//
// where f is described by the rounding mode,
// which must be one of [Trunc], [Floor], [Round] or [Ceil].
// Divide sets z to q if z != nil, updates r if r != nil,
// and returns the pair (z, r) if y != 0.
// If y == 0, a division-by-zero run-time panic occurs.
func (z *Int) Divide(x, y, r *Int, mode RoundingMode) (*Int, *Int) {
var z_abs nat
if z != nil {
z_abs = z.abs
}
var r_neg bool
var r_abs nat
if r != nil {
r_abs = r.abs
}
y_abs := y.abs // save y
if z == y || alias(z_abs, y.abs) {
y_abs = nat(nil).set(y.abs)
}
neg := x.neg != y.neg
z_abs, r_abs = z_abs.div(nil, r_abs, x.abs, y.abs)
if len(r_abs) > 0 {
switch mode {
case Trunc:
r_neg = x.neg
case Floor:
r_neg = y.neg
if neg {
z_abs = z_abs.add(z_abs, natOne)
r_abs = r_abs.sub(y_abs, r_abs)
}
case Ceil:
r_neg = !y.neg
if !neg {
z_abs = z_abs.add(z_abs, natOne)
r_abs = r_abs.sub(y_abs, r_abs)
}
case Round:
switch nat(nil).mul(nil, r_abs, natTwo).cmp(y_abs) {
case -1:
r_neg = x.neg
case 0:
even := len(z_abs) == 0 || z_abs[0]&1 == 0
if even {
r_neg = x.neg
break
}
fallthrough
case 1:
r_neg = !x.neg
z_abs = z_abs.add(z_abs, natOne)
r_abs = r_abs.sub(y_abs, r_abs)
}
default:
panic("unsupported rounding mode")
}
}
if z != nil {
z.abs = z_abs
z.neg = neg && len(z_abs) > 0 // 0 has no sign
}
if r != nil {
r.abs = r_abs
r.neg = r_neg
}
return z, r
}

View file

@ -2011,3 +2011,87 @@ func TestFloat64(t *testing.T) {
}
}
}
func TestIntDivide(t *testing.T) {
x := new(Int)
y := new(Int)
q := new(Int)
r := new(Int)
qExp := new(Int)
rExp := new(Int)
factor, _ := new(Int).SetString("123_456_789_012_345_678_901", 0)
msg := "%v(%v/%v): got q = %v r = %v, want q = %v r = %v"
for i := int64(-10); i <= 10; i++ {
for j := int64(-10); j <= 10; j++ {
if j == 0 {
continue
}
x.SetInt64(i)
y.SetInt64(j)
qExp.SetInt64(i / j)
rExp.SetInt64(i % j)
q, r = q.Divide(x, y, r, Trunc)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "trunc", x, y, q, r, qExp, rExp)
}
x.Mul(x, factor)
y.Mul(y, factor)
rExp.Mul(rExp, factor)
q, r = q.Divide(x, y, r, Trunc)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "trunc", x, y, q, r, qExp, rExp)
}
x.SetInt64(i)
y.SetInt64(j)
floor := int64(math.Floor(float64(i) / float64(j)))
qExp.SetInt64(floor)
rExp.SetInt64(i - j*floor)
q, r = q.Divide(x, y, r, Floor)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "floor", x, y, q, r, qExp, rExp)
}
x.Mul(x, factor)
y.Mul(y, factor)
rExp.Mul(rExp, factor)
q, r = q.Divide(x, y, r, Floor)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "floor", x, y, q, r, qExp, rExp)
}
x.SetInt64(i)
y.SetInt64(j)
ceil := int64(math.Ceil(float64(i) / float64(j)))
qExp.SetInt64(ceil)
rExp.SetInt64(i - j*ceil)
q, r = q.Divide(x, y, r, Ceil)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "ceil", x, y, q, r, qExp, rExp)
}
x.Mul(x, factor)
y.Mul(y, factor)
rExp.Mul(rExp, factor)
q, r = q.Divide(x, y, r, Ceil)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "ceil", x, y, q, r, qExp, rExp)
}
x.SetInt64(i)
y.SetInt64(j)
round := int64(math.RoundToEven(float64(i) / float64(j)))
qExp.SetInt64(round)
rExp.SetInt64(i - j*round)
q, r = q.Divide(x, y, r, Round)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "round", x, y, q, r, qExp, rExp)
}
x.Mul(x, factor)
y.Mul(y, factor)
rExp.Mul(rExp, factor)
q, r = q.Divide(x, y, r, Round)
if q.Cmp(qExp) != 0 || r.Cmp(rExp) != 0 {
t.Errorf(msg, "round", x, y, q, r, qExp, rExp)
}
}
}
}