Skip to content

Commit 5aca051

Browse files
committed
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
1 parent 7f1d62d commit 5aca051

File tree

3 files changed

+395
-4
lines changed

3 files changed

+395
-4
lines changed

src/pkg/math/rand/example_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ func Example_rand() {
8383
// Perm generates a random permutation of the numbers [0, n).
8484
show("Perm", r.Perm(5), r.Perm(5), r.Perm(5))
8585
// Output:
86-
// Float32 0.73793465 0.38461488 0.9940225
87-
// Float64 0.6919607852308565 0.29140004584133117 0.2262092163027547
86+
// Float32 0.2635776 0.6358173 0.6718283
87+
// Float64 0.628605430454327 0.4504798828572669 0.9562755949377957
8888
// ExpFloat64 0.3362240648200941 1.4256072328483647 0.24354758816173044
8989
// NormFloat64 0.17233959114940064 1.577014951434847 0.04259129641113857
9090
// Int31 1501292890 1486668269 182840835

src/pkg/math/rand/rand.go

+38-2
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,46 @@ func (r *Rand) Intn(n int) int {
101101
}
102102

103103
// Float64 returns, as a float64, a pseudo-random number in [0.0,1.0).
104-
func (r *Rand) Float64() float64 { return float64(r.Int63n(1<<53)) / (1 << 53) }
104+
func (r *Rand) Float64() float64 {
105+
// A clearer, simpler implementation would be:
106+
// return float64(r.Int63n(1<<53)) / (1<<53)
107+
// However, Go 1 shipped with
108+
// return float64(r.Int63()) / (1 << 63)
109+
// and we want to preserve that value stream.
110+
//
111+
// There is one bug in the value stream: r.Int63() may be so close
112+
// to 1<<63 that the division rounds up to 1.0, and we've guaranteed
113+
// that the result is always less than 1.0. To fix that, we treat the
114+
// range as cyclic and map 1 back to 0. This is justified by observing
115+
// that while some of the values rounded down to 0, nothing was
116+
// rounding up to 0, so 0 was underrepresented in the results.
117+
// Mapping 1 back to zero restores some balance.
118+
// (The balance is not perfect because the implementation
119+
// returns denormalized numbers for very small r.Int63(),
120+
// and those steal from what would normally be 0 results.)
121+
// The remapping only happens 1/2⁵³ of the time, so most clients
122+
// will not observe it anyway.
123+
f := float64(r.Int63()) / (1 << 63)
124+
if f == 1 {
125+
f = 0
126+
}
127+
return f
128+
}
105129

106130
// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
107-
func (r *Rand) Float32() float32 { return float32(r.Int31n(1<<24)) / (1 << 24) }
131+
func (r *Rand) Float32() float32 {
132+
// Same rationale as in Float64: we want to preserve the Go 1 value
133+
// stream except we want to fix it not to return 1.0
134+
// There is a double rounding going on here, but the argument for
135+
// mapping 1 to 0 still applies: 0 was underrepresented before,
136+
// so mapping 1 to 0 doesn't cause too many 0s.
137+
// This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64).
138+
f := float32(r.Float64())
139+
if f == 1 {
140+
f = 0
141+
}
142+
return f
143+
}
108144

109145
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
110146
func (r *Rand) Perm(n int) []int {

0 commit comments

Comments
 (0)