We realized too late after Go 1 that float64(r.Uint64())/(1<<64)
is not a correct implementation: it occasionally rounds to 1.
The correct implementation is float64(r.Uint64()&(1<<53-1))/(1<<53)
but we couldn't change the implementation for compatibility, so we
changed it to retry only in the "round to 1" cases.
The change to v2 lets us update the algorithm to the simpler,
faster one.
Note that this implementation cannot generate 2⁻⁵⁴, nor 2⁻¹⁰⁰,
nor any of the other numbers between 0 and 2⁻⁵³. A slower algorithm
could shift some of the probability of generating these two boundary
values over to the values in between, but that would be much slower
and not necessarily be better. In particular, the current
implementation has the property that there are uniform gaps between
the possible returned floats, which might help stability. Also, the
result is often scaled and shifted, like Float64()*X+Y. Multiplying by
X>1 would open new gaps, and adding most Y would erase all the
distinctions that were introduced.
The only changes to benchmarks should be in Float32 and Float64.
The other changes remain a cautionary tale.
goos: linux
goarch: amd64
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│
4d84a369d1.amd64 │
2703446c2e.amd64 │
│ sec/op │ sec/op vs base │
SourceUint64-32 1.348n ± 2% 1.337n ± 1% ~ (p=0.662 n=20)
GlobalInt64-32 2.082n ± 2% 2.225n ± 2% +6.87% (p=0.000 n=20)
GlobalInt64Parallel-32 0.1036n ± 1% 0.1043n ± 2% ~ (p=0.171 n=20)
GlobalUint64-32 2.077n ± 2% 2.058n ± 1% ~ (p=0.560 n=20)
GlobalUint64Parallel-32 0.1012n ± 1% 0.1009n ± 1% ~ (p=0.995 n=20)
Int64-32 1.750n ± 0% 1.719n ± 2% -1.74% (p=0.000 n=20)
Uint64-32 1.707n ± 2% 1.669n ± 1% -2.20% (p=0.000 n=20)
GlobalIntN1000-32 3.192n ± 1% 3.321n ± 2% +4.04% (p=0.000 n=20)
IntN1000-32 2.462n ± 2% 2.479n ± 1% ~ (p=0.417 n=20)
Int64N1000-32 2.470n ± 1% 2.477n ± 1% ~ (p=0.664 n=20)
Int64N1e8-32 2.503n ± 2% 2.490n ± 1% ~ (p=0.245 n=20)
Int64N1e9-32 2.487n ± 1% 2.458n ± 1% ~ (p=0.032 n=20)
Int64N2e9-32 2.487n ± 1% 2.486n ± 2% ~ (p=0.507 n=20)
Int64N1e18-32 3.006n ± 2% 3.215n ± 2% +6.94% (p=0.000 n=20)
Int64N2e18-32 3.368n ± 1% 3.588n ± 2% +6.55% (p=0.000 n=20)
Int64N4e18-32 4.763n ± 1% 4.938n ± 2% +3.69% (p=0.000 n=20)
Int32N1000-32 2.403n ± 1% 2.673n ± 2% +11.19% (p=0.000 n=20)
Int32N1e8-32 2.405n ± 1% 2.631n ± 2% +9.42% (p=0.000 n=20)
Int32N1e9-32 2.402n ± 2% 2.628n ± 2% +9.41% (p=0.000 n=20)
Int32N2e9-32 2.384n ± 1% 2.684n ± 2% +12.56% (p=0.000 n=20)
Float32-32 2.641n ± 2% 2.240n ± 2% -15.18% (p=0.000 n=20)
Float64-32 2.483n ± 1% 2.253n ± 1% -9.26% (p=0.000 n=20)
ExpFloat64-32 3.486n ± 2% 3.677n ± 1% +5.49% (p=0.000 n=20)
NormFloat64-32 3.648n ± 1% 3.761n ± 1% +3.11% (p=0.000 n=20)
Perm3-32 33.04n ± 1% 33.55n ± 2% ~ (p=0.180 n=20)
Perm30-32 171.9n ± 1% 173.2n ± 1% ~ (p=0.050 n=20)
Perm30ViaShuffle-32 100.3n ± 1% 115.9n ± 1% +15.55% (p=0.000 n=20)
ShuffleOverhead-32 102.5n ± 1% 101.9n ± 1% ~ (p=0.266 n=20)
Concurrent-32 2.101n ± 0% 2.107n ± 6% ~ (p=0.212 n=20)
goos: darwin
goarch: arm64
pkg: math/rand/v2
cpu: Apple M1
│
4d84a369d1.arm64 │
2703446c2e.arm64 │
│ sec/op │ sec/op vs base │
SourceUint64-8 2.261n ± 1% 2.275n ± 0% ~ (p=0.082 n=20)
GlobalInt64-8 2.160n ± 1% 2.154n ± 1% ~ (p=0.490 n=20)
GlobalInt64Parallel-8 0.4299n ± 0% 0.4298n ± 0% ~ (p=0.663 n=20)
GlobalUint64-8 2.169n ± 1% 2.160n ± 1% ~ (p=0.292 n=20)
GlobalUint64Parallel-8 0.4293n ± 1% 0.4286n ± 0% ~ (p=0.155 n=20)
Int64-8 2.473n ± 1% 2.491n ± 1% ~ (p=0.317 n=20)
Uint64-8 2.453n ± 1% 2.458n ± 0% ~ (p=0.941 n=20)
GlobalIntN1000-8 2.814n ± 2% 2.814n ± 2% ~ (p=0.972 n=20)
IntN1000-8 2.933n ± 2% 2.933n ± 0% ~ (p=0.287 n=20)
Int64N1000-8 2.934n ± 2% 2.962n ± 1% ~ (p=0.062 n=20)
Int64N1e8-8 2.935n ± 2% 2.960n ± 1% ~ (p=0.183 n=20)
Int64N1e9-8 2.934n ± 2% 2.935n ± 2% ~ (p=0.367 n=20)
Int64N2e9-8 2.935n ± 2% 2.934n ± 0% ~ (p=0.455 n=20)
Int64N1e18-8 3.778n ± 1% 3.777n ± 1% ~ (p=0.995 n=20)
Int64N2e18-8 4.359n ± 1% 4.359n ± 1% ~ (p=0.122 n=20)
Int64N4e18-8 6.546n ± 1% 6.536n ± 1% ~ (p=0.920 n=20)
Int32N1000-8 2.940n ± 2% 2.937n ± 0% ~ (p=0.149 n=20)
Int32N1e8-8 2.937n ± 2% 2.937n ± 1% ~ (p=0.620 n=20)
Int32N1e9-8 2.938n ± 0% 2.936n ± 0% ~ (p=0.046 n=20)
Int32N2e9-8 2.938n ± 2% 2.938n ± 2% ~ (p=0.455 n=20)
Float32-8 3.486n ± 0% 3.441n ± 0% -1.28% (p=0.000 n=20)
Float64-8 3.480n ± 0% 3.441n ± 0% -1.13% (p=0.000 n=20)
ExpFloat64-8 4.533n ± 0% 4.486n ± 0% -1.03% (p=0.000 n=20)
NormFloat64-8 4.764n ± 0% 4.721n ± 0% -0.90% (p=0.000 n=20)
Perm3-8 26.66n ± 0% 26.65n ± 0% ~ (p=0.019 n=20)
Perm30-8 143.4n ± 0% 143.2n ± 0% -0.17% (p=0.000 n=20)
Perm30ViaShuffle-8 142.9n ± 0% 143.0n ± 0% ~ (p=0.522 n=20)
ShuffleOverhead-8 120.7n ± 0% 120.6n ± 1% ~ (p=0.488 n=20)
Concurrent-8 2.360n ± 2% 2.399n ± 5% ~ (p=0.062 n=20)
goos: linux
goarch: 386
pkg: math/rand/v2
cpu: AMD Ryzen 9 7950X 16-Core Processor
│
4d84a369d1.386 │
2703446c2e.386 │
│ sec/op │ sec/op vs base │
SourceUint64-32 2.101n ± 2% 2.072n ± 2% ~ (p=0.273 n=20)
GlobalInt64-32 3.518n ± 2% 3.546n ± 27% +0.78% (p=0.007 n=20)
GlobalInt64Parallel-32 0.3206n ± 0% 0.3211n ± 0% ~ (p=0.386 n=20)
GlobalUint64-32 3.538n ± 1% 3.522n ± 2% ~ (p=0.331 n=20)
GlobalUint64Parallel-32 0.3231n ± 0% 0.3172n ± 0% -1.84% (p=0.000 n=20)
Int64-32 2.554n ± 2% 2.520n ± 2% ~ (p=0.465 n=20)
Uint64-32 2.575n ± 2% 2.581n ± 1% ~ (p=0.213 n=20)
GlobalIntN1000-32 6.292n ± 1% 6.171n ± 1% ~ (p=0.015 n=20)
IntN1000-32 4.735n ± 1% 4.752n ± 2% ~ (p=0.635 n=20)
Int64N1000-32 5.489n ± 2% 5.429n ± 1% ~ (p=0.324 n=20)
Int64N1e8-32 5.528n ± 2% 5.469n ± 2% ~ (p=0.013 n=20)
Int64N1e9-32 5.438n ± 2% 5.489n ± 2% ~ (p=0.984 n=20)
Int64N2e9-32 5.474n ± 1% 5.492n ± 2% ~ (p=0.616 n=20)
Int64N1e18-32 9.053n ± 1% 8.927n ± 1% ~ (p=0.037 n=20)
Int64N2e18-32 9.685n ± 2% 9.622n ± 1% ~ (p=0.449 n=20)
Int64N4e18-32 12.18n ± 1% 12.03n ± 1% ~ (p=0.013 n=20)
Int32N1000-32 4.862n ± 1% 4.817n ± 1% -0.94% (p=0.002 n=20)
Int32N1e8-32 4.758n ± 2% 4.801n ± 1% ~ (p=0.597 n=20)
Int32N1e9-32 4.772n ± 1% 4.798n ± 1% ~ (p=0.774 n=20)
Int32N2e9-32 4.847n ± 0% 4.840n ± 1% ~ (p=0.867 n=20)
Float32-32 22.18n ± 4% 10.51n ± 4% -52.61% (p=0.000 n=20)
Float64-32 21.21n ± 3% 20.33n ± 3% -4.17% (p=0.000 n=20)
ExpFloat64-32 12.39n ± 2% 12.59n ± 2% ~ (p=0.139 n=20)
NormFloat64-32 7.422n ± 1% 7.350n ± 2% ~ (p=0.208 n=20)
Perm3-32 38.00n ± 2% 39.29n ± 2% +3.38% (p=0.000 n=20)
Perm30-32 212.7n ± 1% 219.1n ± 2% +3.03% (p=0.001 n=20)
Perm30ViaShuffle-32 187.5n ± 2% 189.8n ± 2% ~ (p=0.457 n=20)
ShuffleOverhead-32 159.7n ± 1% 158.9n ± 2% ~ (p=0.920 n=20)
Concurrent-32 3.470n ± 0% 3.306n ± 3% -4.71% (p=0.000 n=20)
For #61716.
Change-Id: I1933f1f9efd7e6e832d83e7fa5d84398f67d41f5
Reviewed-on: https://go-review.googlesource.com/c/go/+/502503
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
// Perm generates a random permutation of the numbers [0, n).
show("Perm", r.Perm(5), r.Perm(5), r.Perm(5))
// Output:
- // Float32 0.2635776 0.6358173 0.6718283
- // Float64 0.628605430454327 0.4504798828572669 0.9562755949377957
+ // Float32 0.73793465 0.38461488 0.9940225
+ // Float64 0.6919607852308565 0.29140004584133117 0.2262092163027547
// ExpFloat64 0.10400903165715357 0.28855743344575835 0.20489656480442942
// NormFloat64 -0.5602299711828513 -0.9211692958208376 -1.4262061075859056
// Int32 1817075958 91420417 1486590581
// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0).
func (r *Rand) Float64() float64 {
- // A clearer, simpler implementation would be:
- // return float64(r.Int64N(1<<53)) / (1<<53)
- // However, Go 1 shipped with
- // return float64(r.Int64()) / (1 << 63)
- // and we want to preserve that value stream.
- //
- // There is one bug in the value stream: r.Int64() 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.
- //
- // We tried to fix this by mapping 1.0 back to 0.0, but since float64
- // values near 0 are much denser than near 1, mapping 1 to 0 caused
- // a theoretically significant overshoot in the probability of returning 0.
- // Instead of that, if we round up to 1, just try again.
- // Getting 1 only happens 1/2⁵³ of the time, so most clients
- // will not observe it anyway.
-again:
- f := float64(r.Int64()) / (1 << 63)
- if f == 1 {
- goto again // resample; this branch is taken O(never)
- }
- return f
+ // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53).
+ return float64(r.Uint64()<<11>>11) / (1 << 53)
}
// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0).
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
- // This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64).
-again:
- f := float32(r.Float64())
- if f == 1 {
- goto again // resample; this branch is taken O(very rarely)
- }
- return f
+ // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24).
+ return float32(r.Uint32()<<8>>8) / (1 << 24)
}
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers
float64(0.22199446394172706), // ExpFloat64()
float64(2.248962105270165), // ExpFloat64()
- float32(0.6046603), // Float32()
- float32(0.9405091), // Float32()
- float32(0.6645601), // Float32()
- float32(0.4377142), // Float32()
- float32(0.4246375), // Float32()
- float32(0.68682307), // Float32()
- float32(0.06563702), // Float32()
- float32(0.15651925), // Float32()
- float32(0.09696952), // Float32()
- float32(0.30091187), // Float32()
- float32(0.51521266), // Float32()
- float32(0.81363994), // Float32()
- float32(0.21426387), // Float32()
- float32(0.3806572), // Float32()
- float32(0.31805816), // Float32()
- float32(0.46888983), // Float32()
- float32(0.28303415), // Float32()
- float32(0.29310185), // Float32()
- float32(0.67908466), // Float32()
- float32(0.21855305), // Float32()
-
- float64(0.6046602879796196), // Float64()
- float64(0.9405090880450124), // Float64()
- float64(0.6645600532184904), // Float64()
- float64(0.4377141871869802), // Float64()
- float64(0.4246374970712657), // Float64()
- float64(0.6868230728671094), // Float64()
- float64(0.06563701921747622), // Float64()
- float64(0.15651925473279124), // Float64()
- float64(0.09696951891448456), // Float64()
- float64(0.30091186058528707), // Float64()
- float64(0.5152126285020654), // Float64()
- float64(0.8136399609900968), // Float64()
- float64(0.21426387258237492), // Float64()
- float64(0.380657189299686), // Float64()
- float64(0.31805817433032985), // Float64()
- float64(0.4688898449024232), // Float64()
- float64(0.28303415118044517), // Float64()
- float64(0.29310185733681576), // Float64()
- float64(0.6790846759202163), // Float64()
- float64(0.21855305259276428), // Float64()
+ float32(0.39651686), // Float32()
+ float32(0.38516325), // Float32()
+ float32(0.06368679), // Float32()
+ float32(0.027415931), // Float32()
+ float32(0.3535996), // Float32()
+ float32(0.9133533), // Float32()
+ float32(0.40153843), // Float32()
+ float32(0.034464598), // Float32()
+ float32(0.4120984), // Float32()
+ float32(0.51671815), // Float32()
+ float32(0.9472164), // Float32()
+ float32(0.14591497), // Float32()
+ float32(0.42577565), // Float32()
+ float32(0.7241202), // Float32()
+ float32(0.7114463), // Float32()
+ float32(0.01790011), // Float32()
+ float32(0.22837132), // Float32()
+ float32(0.5170377), // Float32()
+ float32(0.9228385), // Float32()
+ float32(0.9747907), // Float32()
+
+ float64(0.17213489113047786), // Float64()
+ float64(0.0813061580926816), // Float64()
+ float64(0.5094944957341486), // Float64()
+ float64(0.2193276794677107), // Float64()
+ float64(0.8287970009760902), // Float64()
+ float64(0.30682661592006877), // Float64()
+ float64(0.21230767869565503), // Float64()
+ float64(0.2757168463782187), // Float64()
+ float64(0.2967873684321951), // Float64()
+ float64(0.13374523933395033), // Float64()
+ float64(0.5777315861149934), // Float64()
+ float64(0.16732005385910476), // Float64()
+ float64(0.40620552435192425), // Float64()
+ float64(0.7929618428784644), // Float64()
+ float64(0.691570514257735), // Float64()
+ float64(0.14320118008134408), // Float64()
+ float64(0.8269708087758376), // Float64()
+ float64(0.13630191289931604), // Float64()
+ float64(0.38270814230149663), // Float64()
+ float64(0.7983258549906352), // Float64()
int64(5577006791947779410), // Int()
int64(8674665223082153551), // Int()