Commit 5aca0514 authored by Russ Cox's avatar Russ Cox

math/rand: restore Go 1.2 value stream for Float32, Float64

CL 22730043 fixed a bug in these functions: they could
return 1.0 despite documentation saying otherwise.
But the fix changed the values returned in the non-buggy case too,
which might invalidate programs depending on a particular
stream when using rand.Seed(0) or when passing their own
Source to rand.New.

The example test says:
        // These tests serve as an example but also make sure we don't change
        // the output of the random number generator when given a fixed seed.
so I think there is some justification for thinking we have
promised not to change the values. In any case, there's no point in
changing the values gratuitously: we can easily fix this bug without
changing the values, and so we should.

That CL just changed the test values too, which defeats the
stated purpose, but it was just a comment.
Add an explicit regression test, which might be
a clearer signal next time that we don't want to change
the values.

Fixes #6721. (again)
Fixes #8013.

LGTM=r
R=iant, r
CC=golang-codereviews
https://golang.org/cl/95460049
parent 7f1d62dc
......@@ -83,8 +83,8 @@ func Example_rand() {
// Perm generates a random permutation of the numbers [0, n).
show("Perm", r.Perm(5), r.Perm(5), r.Perm(5))
// Output:
// Float32 0.73793465 0.38461488 0.9940225
// Float64 0.6919607852308565 0.29140004584133117 0.2262092163027547
// Float32 0.2635776 0.6358173 0.6718283
// Float64 0.628605430454327 0.4504798828572669 0.9562755949377957
// ExpFloat64 0.3362240648200941 1.4256072328483647 0.24354758816173044
// NormFloat64 0.17233959114940064 1.577014951434847 0.04259129641113857
// Int31 1501292890 1486668269 182840835
......
......@@ -101,10 +101,46 @@ func (r *Rand) Intn(n int) int {
}
// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0).
func (r *Rand) Float64() float64 { return float64(r.Int63n(1<<53)) / (1 << 53) }
func (r *Rand) Float64() float64 {
// A clearer, simpler implementation would be:
// return float64(r.Int63n(1<<53)) / (1<<53)
// However, Go 1 shipped with
// return float64(r.Int63()) / (1 << 63)
// and we want to preserve that value stream.
//
// There is one bug in the value stream: r.Int63() may be so close
// to 1<<63 that the division rounds up to 1.0, and we've guaranteed
// that the result is always less than 1.0. To fix that, we treat the
// range as cyclic and map 1 back to 0. This is justified by observing
// that while some of the values rounded down to 0, nothing was
// rounding up to 0, so 0 was underrepresented in the results.
// Mapping 1 back to zero restores some balance.
// (The balance is not perfect because the implementation
// returns denormalized numbers for very small r.Int63(),
// and those steal from what would normally be 0 results.)
// The remapping only happens 1/2⁵³ of the time, so most clients
// will not observe it anyway.
f := float64(r.Int63()) / (1 << 63)
if f == 1 {
f = 0
}
return f
}
// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
func (r *Rand) Float32() float32 { return float32(r.Int31n(1<<24)) / (1 << 24) }
func (r *Rand) Float32() float32 {
// Same rationale as in Float64: we want to preserve the Go 1 value
// stream except we want to fix it not to return 1.0
// There is a double rounding going on here, but the argument for
// mapping 1 to 0 still applies: 0 was underrepresented before,
// so mapping 1 to 0 doesn't cause too many 0s.
// This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64).
f := float32(r.Float64())
if f == 1 {
f = 0
}
return f
}
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
func (r *Rand) Perm(n int) []int {
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment