Skip to content

Commit a52c0a1

Browse files
committed
math/big: make Rat.Denom side-effect free
A Rat is represented via a quotient a/b where a and b are Int values. To make it possible to use an uninitialized Rat value (with a and b uninitialized and thus == 0), the implementation treats a 0 denominator as 1. Rat.Num and Rat.Denom return pointers to these values a and b. Because b may be 0, Rat.Denom used to first initialize it to 1 and thus produce an undesirable side-effect (by changing the Rat's denominator). This CL changes Denom to return a new (not shared) *Int with value 1 in the rare case where the Rat was not initialized. This eliminates the side effect and returns the correct denominator value. While this is changing behavior of the API, the impact should now be minor because together with (prior) CL https://golang.org/cl/202997, which initializes Rats ASAP, Denom is unlikely used to access the denominator of an uninitialized (and thus 0) Rat. Any operation that will somehow set a Rat value will ensure that the denominator is not 0. Fixes #33792. Updates #3521. Change-Id: I0bf15ac60513cf52162bfb62440817ba36f0c3fc Reviewed-on: https://go-review.googlesource.com/c/go/+/203059 Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 4412181 commit a52c0a1

File tree

2 files changed

+35
-8
lines changed

2 files changed

+35
-8
lines changed

src/math/big/rat.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,14 +411,19 @@ func (x *Rat) Num() *Int {
411411
}
412412

413413
// Denom returns the denominator of x; it is always > 0.
414-
// The result is a reference to x's denominator; it
414+
// The result is a reference to x's denominator, unless
415+
// x is an uninitialized (zero value) Rat, in which case
416+
// the result is a new Int of value 1. (To initialize x,
417+
// any operation that sets x will do, including x.Set(x).)
418+
// If the result is a reference to x's denominator it
415419
// may change if a new value is assigned to x, and vice versa.
416-
// If x's denominator is 1, Denom may materialize the denominator, thereby
417-
// modifying x.
418420
func (x *Rat) Denom() *Int {
419421
x.b.neg = false // the result is always >= 0
420422
if len(x.b.abs) == 0 {
421-
x.b.abs = x.b.abs.set(natOne) // materialize denominator (see issue #33792)
423+
// Note: If this proves problematic, we could
424+
// panic instead and require the Rat to
425+
// be explicitly initialized.
426+
return &Int{abs: nat{1}}
422427
}
423428
return &x.b
424429
}

src/math/big/rat_test.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,18 +329,40 @@ func TestIssue3521(t *testing.T) {
329329
t.Errorf("0) got %s want %s", zero.Denom(), one)
330330
}
331331

332-
// 1a) a zero value remains zero independent of denominator
332+
// 1a) the denominator of an (uninitialized) zero value is not shared with the value
333+
s := &zero.b
334+
d := zero.Denom()
335+
if d == s {
336+
t.Errorf("1a) got %s (%p) == %s (%p) want different *Int values", d, d, s, s)
337+
}
338+
339+
// 1b) the denominator of an (uninitialized) value is a new 1 each time
340+
d1 := zero.Denom()
341+
d2 := zero.Denom()
342+
if d1 == d2 {
343+
t.Errorf("1b) got %s (%p) == %s (%p) want different *Int values", d1, d1, d2, d2)
344+
}
345+
346+
// 1c) the denominator of an initialized zero value is shared with the value
333347
x := new(Rat)
348+
x.Set(x) // initialize x (any operation that sets x explicitly will do)
349+
s = &x.b
350+
d = x.Denom()
351+
if d != s {
352+
t.Errorf("1c) got %s (%p) != %s (%p) want identical *Int values", d, d, s, s)
353+
}
354+
355+
// 1d) a zero value remains zero independent of denominator
334356
x.Denom().Set(new(Int).Neg(b))
335357
if x.Cmp(zero) != 0 {
336-
t.Errorf("1a) got %s want %s", x, zero)
358+
t.Errorf("1d) got %s want %s", x, zero)
337359
}
338360

339-
// 1b) a zero value may have a denominator != 0 and != 1
361+
// 1e) a zero value may have a denominator != 0 and != 1
340362
x.Num().Set(a)
341363
qab := new(Rat).SetFrac(a, b)
342364
if x.Cmp(qab) != 0 {
343-
t.Errorf("1b) got %s want %s", x, qab)
365+
t.Errorf("1e) got %s want %s", x, qab)
344366
}
345367

346368
// 2a) an integral value becomes a fraction depending on denominator

0 commit comments

Comments
 (0)