math/big: move Int.Divide and corresponding test function up (cleanup)

This simply moves these two functions next/below the group of related
functions (i.e., Int.Divide is now with the other division functions).
This makes it easier to see its code in context when looking at the
source.

No other code changes.

Follow-up on CL 729860.

Change-Id: Iccfcd014507fa8a68a7d1290ba246b714f8a66a8
Reviewed-on: https://go-review.googlesource.com/c/go/+/779540
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Neal Patel <neal@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Robert Griesemer 2026-05-18 17:20:11 -07:00 committed by Gopher Robot
parent 5f47eb0cdf
commit 93da30397d
2 changed files with 151 additions and 151 deletions

View file

@ -371,6 +371,89 @@ func (z *Int) DivMod(x, y, m *Int) (*Int, *Int) {
return z, m
}
// 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) {
// TODO: optimize the code where z or r is nil
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
}
// Cmp compares x and y and returns:
// - -1 if x < y;
// - 0 if x == y;
@ -1308,86 +1391,3 @@ 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) {
// TODO: optimize the code where z or r is nil
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

@ -505,6 +505,74 @@ func BenchmarkQuoRem(b *testing.B) {
}
}
func TestIntDivide(t *testing.T) {
x := new(Int)
y := new(Int)
q := new(Int)
r := new(Int)
f := new(Int)
qGot := new(Int)
rGot := new(Int)
check := func(i, j, q_ int64, mode RoundingMode, modeName string) {
x.SetInt64(i)
y.SetInt64(j)
q.SetInt64(q_)
r.SetInt64(i - j*q_)
// The quotient remains the same irrespective of scaling factor f,
// everything else gets scaled by f; f is set by the caller.
x.Mul(x, f)
y.Mul(y, f)
r.Mul(r, f)
qGot, rGot = qGot.Divide(x, y, rGot, mode)
if qGot.Cmp(q) != 0 || rGot.Cmp(r) != 0 {
t.Errorf("%v(%v/%v): got q = %v, r = %v; want q = %v, r = %v", modeName, x, y, qGot, rGot, q, r)
}
// nil remainder result
qGot, _ = qGot.Divide(x, y, nil, mode)
if qGot.Cmp(q) != 0 {
t.Errorf("%v(%v/%v): got q = %v; want q = %v", modeName, x, y, qGot, q)
}
// nil quotient result
_, rGot = (*Int)(nil).Divide(x, y, rGot, mode)
if rGot.Cmp(r) != 0 {
t.Errorf("%v(%v/%v): got r = %v; want r = %v", modeName, x, y, rGot, r)
}
// nil quotient and remainder must not panic
(*Int)(nil).Divide(x, y, nil, mode)
}
// test each case with different scaling factors f
for _, s := range []string{
"1",
"1234",
"99991",
"1234567890",
"12345678901234567890",
} {
f.SetString(s, 10)
const n int64 = 10
for i := -n; i <= n; i++ {
for j := -n; j <= n; j++ {
if j == 0 {
continue
}
z := float64(i) / float64(j)
check(i, j, i/j, Trunc, "trunc") // T-division is regular Go integer division
check(i, j, int64(math.Trunc(z)), Trunc, "trunc")
check(i, j, int64(math.Floor(z)), Floor, "floor")
check(i, j, int64(math.Ceil(z)), Ceil, "ceil")
check(i, j, int64(math.RoundToEven(z)), Round, "round")
}
}
}
}
var bitLenTests = []struct {
in string
out int
@ -2011,71 +2079,3 @@ func TestFloat64(t *testing.T) {
}
}
}
func TestIntDivide(t *testing.T) {
x := new(Int)
y := new(Int)
q := new(Int)
r := new(Int)
f := new(Int)
qGot := new(Int)
rGot := new(Int)
check := func(i, j, q_ int64, mode RoundingMode, modeName string) {
x.SetInt64(i)
y.SetInt64(j)
q.SetInt64(q_)
r.SetInt64(i - j*q_)
// The quotient remains the same irrespective of scaling factor f,
// everything else gets scaled by f; f is set by the caller.
x.Mul(x, f)
y.Mul(y, f)
r.Mul(r, f)
qGot, rGot = qGot.Divide(x, y, rGot, mode)
if qGot.Cmp(q) != 0 || rGot.Cmp(r) != 0 {
t.Errorf("%v(%v/%v): got q = %v, r = %v; want q = %v, r = %v", modeName, x, y, qGot, rGot, q, r)
}
// nil remainder result
qGot, _ = qGot.Divide(x, y, nil, mode)
if qGot.Cmp(q) != 0 {
t.Errorf("%v(%v/%v): got q = %v; want q = %v", modeName, x, y, qGot, q)
}
// nil quotient result
_, rGot = (*Int)(nil).Divide(x, y, rGot, mode)
if rGot.Cmp(r) != 0 {
t.Errorf("%v(%v/%v): got r = %v; want r = %v", modeName, x, y, rGot, r)
}
// nil quotient and remainder must not panic
(*Int)(nil).Divide(x, y, nil, mode)
}
// test each case with different scaling factors f
for _, s := range []string{
"1",
"1234",
"99991",
"1234567890",
"12345678901234567890",
} {
f.SetString(s, 10)
const n int64 = 10
for i := -n; i <= n; i++ {
for j := -n; j <= n; j++ {
if j == 0 {
continue
}
z := float64(i) / float64(j)
check(i, j, i/j, Trunc, "trunc") // T-division is regular Go integer division
check(i, j, int64(math.Trunc(z)), Trunc, "trunc")
check(i, j, int64(math.Floor(z)), Floor, "floor")
check(i, j, int64(math.Ceil(z)), Ceil, "ceil")
check(i, j, int64(math.RoundToEven(z)), Round, "round")
}
}
}
}