Skip to content

Commit c266587

Browse files
rscgopherbot
authored andcommitted
math/rand/v2: add, optimize N, UintN, Uint32N, Uint64N
Now that we can break the value stream, we can take advantage of better algorithms that have been suggested since the original code was written. Also optimizes IntN, Int32N, Int64N, Perm (indirectly). All the N variants (IntN, Int32N, Int64N, UintN, N, etc) now return the same values given a Source and parameter n, so that for example uint(r.IntN(10)) and r.UintN(10) and r.N(uint(10)) are completely interchangeable. Int64N4e18 gets slower but that is a near worst case for the algorithm and is extremely unlikely in practice. 32-bit Int32N variants got slower too, by 15-30%, in exchange for speeding up everything on 64-bit systems and consistency across the N functions. Also rename previously missed benchmark GlobalInt63Parallel to GlobalInt64Parallel. goos: linux goarch: amd64 pkg: math/rand/v2 cpu: AMD Ryzen 9 7950X 16-Core Processor │ 11ad9fdddc.amd64 │ 4d84a369d1.amd64 │ │ sec/op │ sec/op vs base │ SourceUint64-32 1.335n ± 1% 1.348n ± 2% ~ (p=0.335 n=20) GlobalInt64-32 2.046n ± 1% 2.082n ± 2% ~ (p=0.310 n=20) GlobalInt63Parallel-32 0.1037n ± 1% GlobalInt64Parallel-32 0.1036n ± 1% GlobalUint64-32 2.075n ± 0% 2.077n ± 2% ~ (p=0.228 n=20) GlobalUint64Parallel-32 0.1013n ± 1% 0.1012n ± 1% ~ (p=0.878 n=20) Int64-32 1.726n ± 2% 1.750n ± 0% +1.39% (p=0.000 n=20) Uint64-32 1.673n ± 1% 1.707n ± 2% +2.03% (p=0.002 n=20) GlobalIntN1000-32 3.895n ± 2% 3.192n ± 1% -18.05% (p=0.000 n=20) IntN1000-32 3.403n ± 1% 2.462n ± 2% -27.65% (p=0.000 n=20) Int64N1000-32 3.053n ± 2% 2.470n ± 1% -19.11% (p=0.000 n=20) Int64N1e8-32 2.718n ± 1% 2.503n ± 2% -7.91% (p=0.000 n=20) Int64N1e9-32 2.712n ± 1% 2.487n ± 1% -8.31% (p=0.000 n=20) Int64N2e9-32 2.690n ± 1% 2.487n ± 1% -7.57% (p=0.000 n=20) Int64N1e18-32 3.084n ± 2% 3.006n ± 2% -2.53% (p=0.000 n=20) Int64N2e18-32 4.026n ± 1% 3.368n ± 1% -16.33% (p=0.000 n=20) Int64N4e18-32 4.049n ± 2% 4.763n ± 1% +17.62% (p=0.000 n=20) Int32N1000-32 2.730n ± 0% 2.403n ± 1% -11.94% (p=0.000 n=20) Int32N1e8-32 2.916n ± 2% 2.405n ± 1% -17.53% (p=0.000 n=20) Int32N1e9-32 3.375n ± 1% 2.402n ± 2% -28.83% (p=0.000 n=20) Int32N2e9-32 3.292n ± 1% 2.384n ± 1% -27.58% (p=0.000 n=20) Float32-32 2.673n ± 1% 2.641n ± 2% ~ (p=0.147 n=20) Float64-32 2.485n ± 1% 2.483n ± 1% ~ (p=0.804 n=20) ExpFloat64-32 3.577n ± 2% 3.486n ± 2% -2.57% (p=0.000 n=20) NormFloat64-32 3.797n ± 2% 3.648n ± 1% -3.92% (p=0.000 n=20) Perm3-32 35.79n ± 2% 33.04n ± 1% -7.68% (p=0.000 n=20) Perm30-32 205.1n ± 1% 171.9n ± 1% -16.14% (p=0.000 n=20) Perm30ViaShuffle-32 111.2n ± 2% 100.3n ± 1% -9.76% (p=0.000 n=20) ShuffleOverhead-32 100.5n ± 2% 102.5n ± 1% +1.99% (p=0.007 n=20) Concurrent-32 2.188n ± 5% 2.101n ± 0% ~ (p=0.013 n=20) goos: darwin goarch: arm64 pkg: math/rand/v2 cpu: Apple M1 │ 11ad9fdddc.arm64 │ 4d84a369d1.arm64 │ │ sec/op │ sec/op vs base │ SourceUint64-8 2.272n ± 1% 2.261n ± 1% ~ (p=0.172 n=20) GlobalInt64-8 2.155n ± 1% 2.160n ± 1% ~ (p=0.482 n=20) GlobalInt63Parallel-8 0.4352n ± 0% GlobalInt64Parallel-8 0.4299n ± 0% GlobalUint64-8 2.173n ± 1% 2.169n ± 1% ~ (p=0.262 n=20) GlobalUint64Parallel-8 0.4340n ± 0% 0.4293n ± 1% -1.08% (p=0.000 n=20) Int64-8 2.544n ± 1% 2.473n ± 1% -2.83% (p=0.000 n=20) Uint64-8 2.552n ± 1% 2.453n ± 1% -3.90% (p=0.000 n=20) GlobalIntN1000-8 3.856n ± 0% 2.814n ± 2% -27.02% (p=0.000 n=20) IntN1000-8 3.820n ± 0% 2.933n ± 2% -23.22% (p=0.000 n=20) Int64N1000-8 3.219n ± 2% 2.934n ± 2% -8.85% (p=0.000 n=20) Int64N1e8-8 3.221n ± 2% 2.935n ± 2% -8.91% (p=0.000 n=20) Int64N1e9-8 3.276n ± 2% 2.934n ± 2% -10.44% (p=0.000 n=20) Int64N2e9-8 3.217n ± 0% 2.935n ± 2% -8.78% (p=0.000 n=20) Int64N1e18-8 3.502n ± 2% 3.778n ± 1% +7.91% (p=0.000 n=20) Int64N2e18-8 4.968n ± 1% 4.359n ± 1% -12.26% (p=0.000 n=20) Int64N4e18-8 4.963n ± 0% 6.546n ± 1% +31.92% (p=0.000 n=20) Int32N1000-8 3.189n ± 1% 2.940n ± 2% -7.81% (p=0.000 n=20) Int32N1e8-8 3.514n ± 1% 2.937n ± 2% -16.41% (p=0.000 n=20) Int32N1e9-8 4.133n ± 0% 2.938n ± 0% -28.91% (p=0.000 n=20) Int32N2e9-8 4.137n ± 0% 2.938n ± 2% -28.97% (p=0.000 n=20) Float32-8 3.468n ± 1% 3.486n ± 0% +0.52% (p=0.000 n=20) Float64-8 3.478n ± 0% 3.480n ± 0% ~ (p=0.063 n=20) ExpFloat64-8 4.563n ± 0% 4.533n ± 0% -0.67% (p=0.000 n=20) NormFloat64-8 4.768n ± 0% 4.764n ± 0% -0.07% (p=0.001 n=20) Perm3-8 28.94n ± 0% 26.66n ± 0% -7.88% (p=0.000 n=20) Perm30-8 175.9n ± 0% 143.4n ± 0% -18.50% (p=0.000 n=20) Perm30ViaShuffle-8 152.6n ± 1% 142.9n ± 0% -6.29% (p=0.000 n=20) ShuffleOverhead-8 119.6n ± 1% 120.7n ± 0% +0.96% (p=0.000 n=20) Concurrent-8 2.452n ± 3% 2.360n ± 2% -3.73% (p=0.007 n=20) goos: linux goarch: 386 pkg: math/rand/v2 cpu: AMD Ryzen 9 7950X 16-Core Processor │ 11ad9fdddc.386 │ 4d84a369d1.386 │ │ sec/op │ sec/op vs base │ SourceUint64-32 2.091n ± 1% 2.101n ± 2% ~ (p=0.672 n=20) GlobalInt64-32 3.514n ± 2% 3.518n ± 2% ~ (p=0.723 n=20) GlobalInt63Parallel-32 0.3197n ± 0% GlobalInt64Parallel-32 0.3206n ± 0% GlobalUint64-32 3.542n ± 1% 3.538n ± 1% ~ (p=0.304 n=20) GlobalUint64Parallel-32 0.3218n ± 0% 0.3231n ± 0% ~ (p=0.071 n=20) Int64-32 2.552n ± 2% 2.554n ± 2% ~ (p=0.693 n=20) Uint64-32 2.566n ± 1% 2.575n ± 2% ~ (p=0.606 n=20) GlobalIntN1000-32 5.965n ± 2% 6.292n ± 1% +5.46% (p=0.000 n=20) IntN1000-32 4.652n ± 1% 4.735n ± 1% +1.77% (p=0.000 n=20) Int64N1000-32 14.485n ± 1% 5.489n ± 2% -62.11% (p=0.000 n=20) Int64N1e8-32 14.675n ± 1% 5.528n ± 2% -62.33% (p=0.000 n=20) Int64N1e9-32 16.805n ± 2% 5.438n ± 2% -67.64% (p=0.000 n=20) Int64N2e9-32 14.515n ± 1% 5.474n ± 1% -62.28% (p=0.000 n=20) Int64N1e18-32 16.165n ± 1% 9.053n ± 1% -44.00% (p=0.000 n=20) Int64N2e18-32 17.945n ± 2% 9.685n ± 2% -46.03% (p=0.000 n=20) Int64N4e18-32 18.35n ± 2% 12.18n ± 1% -33.62% (p=0.000 n=20) Int32N1000-32 3.608n ± 1% 4.862n ± 1% +34.77% (p=0.000 n=20) Int32N1e8-32 3.767n ± 1% 4.758n ± 2% +26.31% (p=0.000 n=20) Int32N1e9-32 4.130n ± 2% 4.772n ± 1% +15.54% (p=0.000 n=20) Int32N2e9-32 4.206n ± 1% 4.847n ± 0% +15.24% (p=0.000 n=20) Float32-32 22.18n ± 4% 22.18n ± 4% ~ (p=0.195 n=20) Float64-32 20.75n ± 4% 21.21n ± 3% ~ (p=0.394 n=20) ExpFloat64-32 12.58n ± 3% 12.39n ± 2% ~ (p=0.032 n=20) NormFloat64-32 7.920n ± 3% 7.422n ± 1% -6.29% (p=0.000 n=20) Perm3-32 40.27n ± 1% 38.00n ± 2% -5.65% (p=0.000 n=20) Perm30-32 213.2n ± 2% 212.7n ± 1% ~ (p=0.995 n=20) Perm30ViaShuffle-32 164.2n ± 2% 187.5n ± 2% +14.22% (p=0.000 n=20) ShuffleOverhead-32 134.7n ± 2% 159.7n ± 1% +18.52% (p=0.000 n=20) Concurrent-32 3.301n ± 2% 3.470n ± 0% +5.10% (p=0.000 n=20) For #61716. Change-Id: Id1481b04202883cd0b23e21bb58d1bca4e482bd3 Reviewed-on: https://go-review.googlesource.com/c/go/+/502500 Reviewed-by: Rob Pike <[email protected]> Auto-Submit: Russ Cox <[email protected]> Reviewed-by: David Chase <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent c7dddb0 commit c266587

File tree

6 files changed

+406
-215
lines changed

6 files changed

+406
-215
lines changed

api/next/61716.txt

+7
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ pkg math/rand/v2, func Int32N(int32) int32 #61716
77
pkg math/rand/v2, func Int64() int64 #61716
88
pkg math/rand/v2, func Int64N(int64) int64 #61716
99
pkg math/rand/v2, func IntN(int) int #61716
10+
pkg math/rand/v2, func N[$0 intType]($0) $0 #61716
1011
pkg math/rand/v2, func New(Source) *Rand #61716
1112
pkg math/rand/v2, func NewSource(int64) Source #61716
1213
pkg math/rand/v2, func NewZipf(*Rand, float64, float64, uint64) *Zipf #61716
1314
pkg math/rand/v2, func NormFloat64() float64 #61716
1415
pkg math/rand/v2, func Perm(int) []int #61716
1516
pkg math/rand/v2, func Shuffle(int, func(int, int)) #61716
1617
pkg math/rand/v2, func Uint32() uint32 #61716
18+
pkg math/rand/v2, func Uint32N(uint32) uint32 #61716
1719
pkg math/rand/v2, func Uint64() uint64 #61716
20+
pkg math/rand/v2, func Uint64N(uint64) uint64 #61716
21+
pkg math/rand/v2, func UintN(uint) uint #61716
1822
pkg math/rand/v2, method (*Rand) ExpFloat64() float64 #61716
1923
pkg math/rand/v2, method (*Rand) Float32() float32 #61716
2024
pkg math/rand/v2, method (*Rand) Float64() float64 #61716
@@ -28,7 +32,10 @@ pkg math/rand/v2, method (*Rand) NormFloat64() float64 #61716
2832
pkg math/rand/v2, method (*Rand) Perm(int) []int #61716
2933
pkg math/rand/v2, method (*Rand) Shuffle(int, func(int, int)) #61716
3034
pkg math/rand/v2, method (*Rand) Uint32() uint32 #61716
35+
pkg math/rand/v2, method (*Rand) Uint32N(uint32) uint32 #61716
3136
pkg math/rand/v2, method (*Rand) Uint64() uint64 #61716
37+
pkg math/rand/v2, method (*Rand) Uint64N(uint64) uint64 #61716
38+
pkg math/rand/v2, method (*Rand) UintN(uint) uint #61716
3239
pkg math/rand/v2, method (*Zipf) Uint64() uint64 #61716
3340
pkg math/rand/v2, type Rand struct #61716
3441
pkg math/rand/v2, type Source interface { Uint64 } #61716

src/math/rand/v2/example_test.go

+18-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"strings"
1212
"text/tabwriter"
13+
"time"
1314
)
1415

1516
// These tests serve as an example but also make sure we don't change
@@ -84,15 +85,15 @@ func Example_rand() {
8485
// Output:
8586
// Float32 0.2635776 0.6358173 0.6718283
8687
// Float64 0.628605430454327 0.4504798828572669 0.9562755949377957
87-
// ExpFloat64 0.3362240648200941 1.4256072328483647 0.24354758816173044
88-
// NormFloat64 0.17233959114940064 1.577014951434847 0.04259129641113857
89-
// Int32 1501292890 1486668269 182840835
90-
// Int64 3546343826724305832 5724354148158589552 5239846799706671610
91-
// Uint32 2760229429 296659907 1922395059
92-
// IntN(10) 1 2 5
93-
// Int32N(10) 4 7 8
94-
// Int64N(10) 7 6 3
95-
// Perm [1 4 2 3 0] [4 2 1 3 0] [1 2 4 0 3]
88+
// ExpFloat64 0.10400903165715357 0.28855743344575835 0.20489656480442942
89+
// NormFloat64 -0.5602299711828513 -0.9211692958208376 -1.4262061075859056
90+
// Int32 1817075958 91420417 1486590581
91+
// Int64 5724354148158589552 5239846799706671610 5927547564735367388
92+
// Uint32 2295813601 961197529 3493134579
93+
// IntN(10) 4 5 1
94+
// Int32N(10) 8 5 4
95+
// Int64N(10) 2 6 3
96+
// Perm [3 4 2 1 0] [4 1 2 0 3] [0 2 1 3 4]
9697
}
9798

9899
func ExamplePerm() {
@@ -105,6 +106,14 @@ func ExamplePerm() {
105106
// 0
106107
}
107108

109+
func ExampleN() {
110+
// Print an int64 in the half-open interval [0, 100).
111+
fmt.Println(rand.N(int64(100)))
112+
113+
// Sleep for a random duration between 0 and 100 milliseconds.
114+
time.Sleep(rand.N(100 * time.Millisecond))
115+
}
116+
108117
func ExampleShuffle() {
109118
words := strings.Fields("ink runs from the corners of my mouth")
110119
rand.Shuffle(len(words), func(i, j int) {

src/math/rand/v2/export_test.go

-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44

55
package rand
66

7-
func Int32NForTest(r *Rand, n int32) int32 {
8-
return r.int31n(n)
9-
}
10-
117
func GetNormalDistributionParameters() (float64, [128]uint32, [128]float32, [128]float32) {
128
return rn, kn, wn, fn
139
}

src/math/rand/v2/rand.go

+154-58
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package rand
1919

2020
import (
21+
"math/bits"
2122
_ "unsafe" // for go:linkname
2223
)
2324

@@ -58,37 +59,122 @@ func New(src Source) *Rand {
5859
func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) }
5960

6061
// Uint32 returns a pseudo-random 32-bit value as a uint32.
61-
func (r *Rand) Uint32() uint32 { return uint32(r.Int64() >> 31) }
62+
func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) }
6263

6364
// Uint64 returns a pseudo-random 64-bit value as a uint64.
64-
func (r *Rand) Uint64() uint64 {
65-
return r.src.Uint64()
66-
}
65+
func (r *Rand) Uint64() uint64 { return r.src.Uint64() }
6766

6867
// Int32 returns a non-negative pseudo-random 31-bit integer as an int32.
69-
func (r *Rand) Int32() int32 { return int32(r.Int64() >> 32) }
68+
func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) }
7069

7170
// Int returns a non-negative pseudo-random int.
72-
func (r *Rand) Int() int {
73-
u := uint(r.Int64())
74-
return int(u << 1 >> 1) // clear sign bit if int == int32
75-
}
71+
func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) }
7672

7773
// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n).
7874
// It panics if n <= 0.
7975
func (r *Rand) Int64N(n int64) int64 {
8076
if n <= 0 {
8177
panic("invalid argument to Int64N")
8278
}
79+
return int64(r.uint64n(uint64(n)))
80+
}
81+
82+
// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n).
83+
// It panics if n == 0.
84+
func (r *Rand) Uint64N(n uint64) uint64 {
85+
if n == 0 {
86+
panic("invalid argument to Uint64N")
87+
}
88+
return r.uint64n(n)
89+
}
90+
91+
// uint64n is the no-bounds-checks version of Uint64N.
92+
func (r *Rand) uint64n(n uint64) uint64 {
93+
if is32bit && uint64(uint32(n)) == n {
94+
return uint64(r.uint32n(uint32(n)))
95+
}
8396
if n&(n-1) == 0 { // n is power of two, can mask
84-
return r.Int64() & (n - 1)
97+
return r.Uint64() & (n - 1)
8598
}
86-
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
87-
v := r.Int64()
88-
for v > max {
89-
v = r.Int64()
99+
100+
// Suppose we have a uint64 x uniform in the range [0,2⁶⁴)
101+
// and want to reduce it to the range [0,n) preserving exact uniformity.
102+
// We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by
103+
// the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴.
104+
// Since there are 2⁶⁴ possible inputs x and only n possible outputs,
105+
// the output is necessarily biased if n does not divide 2⁶⁴.
106+
// In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴).
107+
// There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products
108+
// in that range, depending on k.
109+
// But suppose we reject the sample and try again when
110+
// x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible
111+
// outcomes out of the 2⁶⁴.
112+
// Now there are exactly floor(2⁶⁴/n) possible ways to produce
113+
// each output value k, so we've restored uniformity.
114+
// To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n,
115+
// so the direct implementation of this algorithm would be:
116+
//
117+
// hi, lo := bits.Mul64(r.Uint64(), n)
118+
// thresh := -n % n
119+
// for lo < thresh {
120+
// hi, lo = bits.Mul64(r.Uint64(), n)
121+
// }
122+
//
123+
// That still leaves an expensive 64-bit division that we would rather avoid.
124+
// We know that thresh < n, and n is usually much less than 2⁶⁴, so we can
125+
// avoid the last four lines unless lo < n.
126+
//
127+
// See also:
128+
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
129+
// https://lemire.me/blog/2016/06/30/fast-random-shuffling
130+
hi, lo := bits.Mul64(r.Uint64(), n)
131+
if lo < n {
132+
thresh := -n % n
133+
for lo < thresh {
134+
hi, lo = bits.Mul64(r.Uint64(), n)
135+
}
90136
}
91-
return v % n
137+
return hi
138+
}
139+
140+
// uint32n is an identical computation to uint64n
141+
// but optimized for 32-bit systems.
142+
func (r *Rand) uint32n(n uint32) uint32 {
143+
if n&(n-1) == 0 { // n is power of two, can mask
144+
return uint32(r.Uint64()) & (n - 1)
145+
}
146+
// On 64-bit systems we still use the uint64 code below because
147+
// the probability of a random uint64 lo being < a uint32 n is near zero,
148+
// meaning the unbiasing loop almost never runs.
149+
// On 32-bit systems, here we need to implement that same logic in 32-bit math,
150+
// both to preserve the exact output sequence observed on 64-bit machines
151+
// and to preserve the optimization that the unbiasing loop almost never runs.
152+
//
153+
// We want to compute
154+
// hi, lo := bits.Mul64(r.Uint64(), n)
155+
// In terms of 32-bit halves, this is:
156+
// x1:x0 := r.Uint64()
157+
// 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n)
158+
// Writing out the multiplication in terms of bits.Mul32 allows
159+
// using direct hardware instructions and avoiding
160+
// the computations involving these zeros.
161+
x := r.Uint64()
162+
lo1a, lo0 := bits.Mul32(uint32(x), n)
163+
hi, lo1b := bits.Mul32(uint32(x>>32), n)
164+
lo1, c := bits.Add32(lo1a, lo1b, 0)
165+
hi += c
166+
if lo1 == 0 && lo0 < uint32(n) {
167+
n64 := uint64(n)
168+
thresh := uint32(-n64 % n64)
169+
for lo1 == 0 && lo0 < thresh {
170+
x := r.Uint64()
171+
lo1a, lo0 = bits.Mul32(uint32(x), n)
172+
hi, lo1b = bits.Mul32(uint32(x>>32), n)
173+
lo1, c = bits.Add32(lo1a, lo1b, 0)
174+
hi += c
175+
}
176+
}
177+
return hi
92178
}
93179

94180
// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n).
@@ -97,51 +183,36 @@ func (r *Rand) Int32N(n int32) int32 {
97183
if n <= 0 {
98184
panic("invalid argument to Int32N")
99185
}
100-
if n&(n-1) == 0 { // n is power of two, can mask
101-
return r.Int32() & (n - 1)
102-
}
103-
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
104-
v := r.Int32()
105-
for v > max {
106-
v = r.Int32()
107-
}
108-
return v % n
186+
return int32(r.uint64n(uint64(n)))
109187
}
110188

111-
// int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n).
112-
// n must be > 0, but int31n does not check this; the caller must ensure it.
113-
// int31n exists because Int32N is inefficient, but Go 1 compatibility
114-
// requires that the stream of values produced by math/rand/v2 remain unchanged.
115-
// int31n can thus only be used internally, by newly introduced APIs.
116-
//
117-
// For implementation details, see:
118-
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
119-
// https://lemire.me/blog/2016/06/30/fast-random-shuffling
120-
func (r *Rand) int31n(n int32) int32 {
121-
v := r.Uint32()
122-
prod := uint64(v) * uint64(n)
123-
low := uint32(prod)
124-
if low < uint32(n) {
125-
thresh := uint32(-n) % uint32(n)
126-
for low < thresh {
127-
v = r.Uint32()
128-
prod = uint64(v) * uint64(n)
129-
low = uint32(prod)
130-
}
189+
// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n).
190+
// It panics if n == 0.
191+
func (r *Rand) Uint32N(n uint32) uint32 {
192+
if n == 0 {
193+
panic("invalid argument to Uint32N")
131194
}
132-
return int32(prod >> 32)
195+
return uint32(r.uint64n(uint64(n)))
133196
}
134197

198+
const is32bit = ^uint(0)>>32 == 0
199+
135200
// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n).
136201
// It panics if n <= 0.
137202
func (r *Rand) IntN(n int) int {
138203
if n <= 0 {
139204
panic("invalid argument to IntN")
140205
}
141-
if n <= 1<<31-1 {
142-
return int(r.Int32N(int32(n)))
206+
return int(r.uint64n(uint64(n)))
207+
}
208+
209+
// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n).
210+
// It panics if n == 0.
211+
func (r *Rand) UintN(n uint) uint {
212+
if n == 0 {
213+
panic("invalid argument to UintN")
143214
}
144-
return int(r.Int64N(int64(n)))
215+
return uint(r.uint64n(uint64(n)))
145216
}
146217

147218
// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0).
@@ -214,13 +285,8 @@ func (r *Rand) Shuffle(n int, swap func(i, j int)) {
214285
// there's no way that any PRNG can have a big enough internal state to
215286
// generate even a minuscule percentage of the possible permutations.
216287
// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
217-
i := n - 1
218-
for ; i > 1<<31-1-1; i-- {
219-
j := int(r.Int64N(int64(i + 1)))
220-
swap(i, j)
221-
}
222-
for ; i > 0; i-- {
223-
j := int(r.int31n(int32(i + 1)))
288+
for i := n - 1; i > 0; i-- {
289+
j := int(r.uint64n(uint64(i + 1)))
224290
swap(i, j)
225291
}
226292
}
@@ -255,6 +321,16 @@ func Int64() int64 { return globalRand.Int64() }
255321
// from the default Source.
256322
func Uint32() uint32 { return globalRand.Uint32() }
257323

324+
// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n)
325+
// from the default Source.
326+
// It panics if n <= 0.
327+
func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) }
328+
329+
// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n)
330+
// from the default Source.
331+
// It panics if n <= 0.
332+
func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) }
333+
258334
// Uint64 returns a pseudo-random 64-bit value as a uint64
259335
// from the default Source.
260336
func Uint64() uint64 { return globalRand.Uint64() }
@@ -266,21 +342,41 @@ func Int32() int32 { return globalRand.Int32() }
266342
// Int returns a non-negative pseudo-random int from the default Source.
267343
func Int() int { return globalRand.Int() }
268344

269-
// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n)
345+
// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n)
270346
// from the default Source.
271347
// It panics if n <= 0.
272348
func Int64N(n int64) int64 { return globalRand.Int64N(n) }
273349

274-
// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n)
350+
// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n)
275351
// from the default Source.
276352
// It panics if n <= 0.
277353
func Int32N(n int32) int32 { return globalRand.Int32N(n) }
278354

279-
// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n)
355+
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
280356
// from the default Source.
281357
// It panics if n <= 0.
282358
func IntN(n int) int { return globalRand.IntN(n) }
283359

360+
// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n)
361+
// from the default Source.
362+
// It panics if n <= 0.
363+
func UintN(n uint) uint { return globalRand.UintN(n) }
364+
365+
// N returns a pseudo-random number in the half-open interval [0,n) from the default Source.
366+
// The type parameter Int can be any integer type.
367+
// It panics if n <= 0.
368+
func N[Int intType](n Int) Int {
369+
if n <= 0 {
370+
panic("invalid argument to N")
371+
}
372+
return Int(globalRand.uint64n(uint64(n)))
373+
}
374+
375+
type intType interface {
376+
~int | ~int8 | ~int16 | ~int32 | ~int64 |
377+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
378+
}
379+
284380
// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0)
285381
// from the default Source.
286382
func Float64() float64 { return globalRand.Float64() }

0 commit comments

Comments
 (0)