Skip to content

Commit a645b20

Browse files
authored
Merge branch 'main' into bryphe/fix/css-hydration-errors
2 parents 7f3f409 + 6e6eee6 commit a645b20

File tree

6 files changed

+649
-2
lines changed

6 files changed

+649
-2
lines changed

cryptorand/numbers.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package cryptorand
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/binary"
6+
7+
"golang.org/x/xerrors"
8+
)
9+
10+
// Most of this code is inspired by math/rand, so shares similar
11+
// functions and implementations, but uses crypto/rand to generate
12+
// random Int63 data.
13+
14+
// Int64 returns a non-negative random 63-bit integer as a int64.
15+
func Int63() (int64, error) {
16+
var i int64
17+
err := binary.Read(rand.Reader, binary.BigEndian, &i)
18+
if err != nil {
19+
return 0, xerrors.Errorf("read binary: %w", err)
20+
}
21+
22+
if i < 0 {
23+
return -i, nil
24+
}
25+
return i, nil
26+
}
27+
28+
// Uint64 returns a random 64-bit integer as a uint64.
29+
func Uint64() (uint64, error) {
30+
upper, err := Int63()
31+
if err != nil {
32+
return 0, xerrors.Errorf("read upper: %w", err)
33+
}
34+
35+
lower, err := Int63()
36+
if err != nil {
37+
return 0, xerrors.Errorf("read lower: %w", err)
38+
}
39+
40+
return uint64(lower)>>31 | uint64(upper)<<32, nil
41+
}
42+
43+
// Int31 returns a non-negative random 31-bit integer as a int32.
44+
func Int31() (int32, error) {
45+
i, err := Int63()
46+
if err != nil {
47+
return 0, err
48+
}
49+
50+
return int32(i >> 32), nil
51+
}
52+
53+
// Uint32 returns a 32-bit value as a uint32.
54+
func Uint32() (uint32, error) {
55+
i, err := Int63()
56+
if err != nil {
57+
return 0, err
58+
}
59+
60+
return uint32(i >> 31), nil
61+
}
62+
63+
// Int returns a non-negative random integer as a int.
64+
func Int() (int, error) {
65+
i, err := Int63()
66+
if err != nil {
67+
return 0, err
68+
}
69+
70+
if i < 0 {
71+
return int(-i), nil
72+
}
73+
return int(i), nil
74+
}
75+
76+
// Int63n returns a non-negative random integer in [0,n) as a int64.
77+
func Int63n(n int64) (int64, error) {
78+
if n <= 0 {
79+
panic("invalid argument to Int63n")
80+
}
81+
82+
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
83+
i, err := Int63()
84+
if err != nil {
85+
return 0, err
86+
}
87+
88+
for i > max {
89+
i, err = Int63()
90+
if err != nil {
91+
return 0, err
92+
}
93+
}
94+
95+
return i % n, nil
96+
}
97+
98+
// Int31n returns a non-negative integer in [0,n) as a int32.
99+
func Int31n(n int32) (int32, error) {
100+
i, err := Uint32()
101+
if err != nil {
102+
return 0, err
103+
}
104+
105+
return UnbiasedModulo32(i, n)
106+
}
107+
108+
// UnbiasedModulo32 uniformly modulos v by n over a sufficiently large data
109+
// set, regenerating v if necessary. n must be > 0. All input bits in v must be
110+
// fully random, you cannot cast a random uint8/uint16 for input into this
111+
// function.
112+
func UnbiasedModulo32(v uint32, n int32) (int32, error) {
113+
prod := uint64(v) * uint64(n)
114+
low := uint32(prod)
115+
if low < uint32(n) {
116+
thresh := uint32(-n) % uint32(n)
117+
for low < thresh {
118+
var err error
119+
v, err = Uint32()
120+
if err != nil {
121+
return 0, err
122+
}
123+
prod = uint64(v) * uint64(n)
124+
low = uint32(prod)
125+
}
126+
}
127+
return int32(prod >> 32), nil
128+
}
129+
130+
// Intn returns a non-negative integer in [0,n) as a int.
131+
func Intn(n int) (int, error) {
132+
if n <= 0 {
133+
panic("n must be a positive nonzero number")
134+
}
135+
136+
if n <= 1<<31-1 {
137+
i, err := Int31n(int32(n))
138+
if err != nil {
139+
return 0, err
140+
}
141+
142+
return int(i), nil
143+
}
144+
145+
i, err := Int63n(int64(n))
146+
if err != nil {
147+
return 0, err
148+
}
149+
150+
return int(i), nil
151+
}
152+
153+
// Float64 returns a random number in [0.0,1.0) as a float64.
154+
func Float64() (float64, error) {
155+
again:
156+
i, err := Int63n(1 << 53)
157+
if err != nil {
158+
return 0, err
159+
}
160+
161+
f := (float64(i) / (1 << 53))
162+
if f == 1 {
163+
goto again
164+
}
165+
166+
return f, nil
167+
}
168+
169+
// Float32 returns a random number in [0.0,1.0) as a float32.
170+
func Float32() (float32, error) {
171+
again:
172+
i, err := Float64()
173+
if err != nil {
174+
return 0, err
175+
}
176+
177+
f := float32(i)
178+
if f == 1 {
179+
goto again
180+
}
181+
182+
return f, nil
183+
}
184+
185+
// Bool returns a random true/false value as a bool.
186+
func Bool() (bool, error) {
187+
i, err := Uint64()
188+
if err != nil {
189+
return false, err
190+
}
191+
192+
// True if the least significant bit is 1
193+
return i&1 == 1, nil
194+
}

cryptorand/numbers_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package cryptorand_test
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/binary"
6+
"testing"
7+
8+
"github.com/coder/coder/cryptorand"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestInt63(t *testing.T) {
13+
t.Parallel()
14+
15+
for i := 0; i < 20; i++ {
16+
v, err := cryptorand.Int63()
17+
require.NoError(t, err, "unexpected error from Int63")
18+
t.Logf("value: %v <- random?", v)
19+
require.True(t, v >= 0, "values must be positive")
20+
}
21+
}
22+
23+
func TestUint64(t *testing.T) {
24+
t.Parallel()
25+
26+
for i := 0; i < 20; i++ {
27+
v, err := cryptorand.Uint64()
28+
require.NoError(t, err, "unexpected error from Uint64")
29+
t.Logf("value: %v <- random?", v)
30+
}
31+
}
32+
33+
func TestInt31(t *testing.T) {
34+
t.Parallel()
35+
36+
for i := 0; i < 20; i++ {
37+
v, err := cryptorand.Int31()
38+
require.NoError(t, err, "unexpected error from Int31")
39+
t.Logf("value: %v <- random?", v)
40+
require.True(t, v >= 0, "values must be positive")
41+
}
42+
}
43+
44+
func TestUnbiasedModulo32(t *testing.T) {
45+
t.Parallel()
46+
const mod = 7
47+
dist := [mod]uint32{}
48+
49+
for i := 0; i < 1000; i++ {
50+
b := [4]byte{}
51+
_, _ = rand.Read(b[:])
52+
v, err := cryptorand.UnbiasedModulo32(binary.BigEndian.Uint32(b[:]), mod)
53+
require.NoError(t, err, "unexpected error from UnbiasedModulo32")
54+
dist[v]++
55+
}
56+
57+
t.Logf("dist: %+v <- evenly distributed?", dist)
58+
}
59+
60+
func TestUint32(t *testing.T) {
61+
t.Parallel()
62+
63+
for i := 0; i < 20; i++ {
64+
v, err := cryptorand.Uint32()
65+
require.NoError(t, err, "unexpected error from Uint32")
66+
t.Logf("value: %v <- random?", v)
67+
}
68+
}
69+
70+
func TestInt(t *testing.T) {
71+
t.Parallel()
72+
73+
for i := 0; i < 20; i++ {
74+
v, err := cryptorand.Int()
75+
require.NoError(t, err, "unexpected error from Int")
76+
t.Logf("value: %v <- random?", v)
77+
require.True(t, v >= 0, "values must be positive")
78+
}
79+
}
80+
81+
func TestInt63n(t *testing.T) {
82+
t.Parallel()
83+
84+
for i := 0; i < 20; i++ {
85+
v, err := cryptorand.Int63n(1 << 35)
86+
require.NoError(t, err, "unexpected error from Int63n")
87+
t.Logf("value: %v <- random?", v)
88+
require.True(t, v >= 0, "values must be positive")
89+
require.True(t, v < 1<<35, "values must be less than 1<<35")
90+
}
91+
}
92+
93+
func TestInt31n(t *testing.T) {
94+
t.Parallel()
95+
96+
for i := 0; i < 20; i++ {
97+
v, err := cryptorand.Int31n(100)
98+
require.NoError(t, err, "unexpected error from Int31n")
99+
t.Logf("value: %v <- random?", v)
100+
require.True(t, v >= 0, "values must be positive")
101+
require.True(t, v < 100, "values must be less than 100")
102+
}
103+
}
104+
105+
func TestIntn(t *testing.T) {
106+
t.Parallel()
107+
108+
for i := 0; i < 20; i++ {
109+
v, err := cryptorand.Intn(100)
110+
require.NoError(t, err, "unexpected error from Intn")
111+
t.Logf("value: %v <- random?", v)
112+
require.True(t, v >= 0, "values must be positive")
113+
require.True(t, v < 100, "values must be less than 100")
114+
}
115+
}
116+
117+
func TestFloat64(t *testing.T) {
118+
t.Parallel()
119+
120+
for i := 0; i < 20; i++ {
121+
v, err := cryptorand.Float64()
122+
require.NoError(t, err, "unexpected error from Float64")
123+
t.Logf("value: %v <- random?", v)
124+
require.True(t, v >= 0.0, "values must be positive")
125+
require.True(t, v < 1.0, "values must be less than 1.0")
126+
}
127+
}
128+
129+
func TestFloat32(t *testing.T) {
130+
t.Parallel()
131+
132+
for i := 0; i < 20; i++ {
133+
v, err := cryptorand.Float32()
134+
require.NoError(t, err, "unexpected error from Float32")
135+
t.Logf("value: %v <- random?", v)
136+
require.True(t, v >= 0.0, "values must be positive")
137+
require.True(t, v < 1.0, "values must be less than 1.0")
138+
}
139+
}
140+
141+
func TestBool(t *testing.T) {
142+
t.Parallel()
143+
144+
const iterations = 10000
145+
trueCount := 0
146+
147+
for i := 0; i < iterations; i += 1 {
148+
v, err := cryptorand.Bool()
149+
require.NoError(t, err, "unexpected error from Bool")
150+
if v {
151+
trueCount++
152+
}
153+
}
154+
155+
percentage := (float64(trueCount) / iterations) * 100
156+
t.Logf("number of true values: %d of %d total (%.2f%%)", trueCount, iterations, percentage)
157+
require.True(t, percentage > 48, "expected more than 48 percent of values to be true")
158+
require.True(t, percentage < 52, "expected less than 52 percent of values to be true")
159+
}

0 commit comments

Comments
 (0)