Learning Golang: Random numbers
This is part 23 of my journey learning Golang.
Random number generators
For some kinds of programs, like simulations, games, or test code, it is useful to be able to generate unpredictable sequences of numbers.
While generating truly random numbers is a hard problem (see RANDOM.ORG), a common alternative is to use a pseudorandom number generator.
Go has a package for generating pseudo-random numbers: rand.
Random integer
The Int()
function from the rand
package returns a non-negative pseudo-random int
.
For example:
1package main
2
3import (
4 "fmt"
5 "math/rand"
6 "time"
7)
8
9func main() {
10 rand.Seed(time.Now().UnixNano())
11 for i := 0; i < 3; i++ {
12 r := rand.Int()
13 fmt.Println(r)
14 }
15}
Source: learning-go/random/random-integer.go
When executed it produces output like this:
1❯ bin/go run ./random/random-integer.go
27844980600027973356
33488658292559818292
43150213459654129095
5
6❯ bin/go run ./random/random-integer.go
78417580983999923480
87072937072426873398
98910705581888374081
The Int()
function is being called 3 times, and each time it produces a different value.
Notice that the Seed()
function is being used to initialize the pseudo-random generator with an integer value
corresponding to the current time. That makes the generator produce a different sequence each time the program is
executed. Without that, it would always use the same seed value, and produce the same numbers.
Keep in mind that the pseudo-random number generator is deterministic, not truly random. That’s why if we want different results, we must configure it to start from different starting points.
Cryptographically strong random seed
Using the current time as random seed, like the program above does, is not good enough if the random numbers that will be generated need to be hard to predict or tamper with. See this Stack Overflow answer for a brief explanation of the problem.
The crypto/rand
package implements a cryptographically secure random number generator. It is based on secure random
number generators provided by the underlying operating system. Read about entropy
to learn more.
For instance take a look at this program:
1package main
2
3import (
4 "encoding/binary"
5 "fmt"
6 crypto_rand "crypto/rand"
7 math_rand "math/rand"
8)
9
10func main() {
11 seedRandomizer()
12 for i := 0; i < 3; i++ {
13 r := math_rand.Int()
14 fmt.Println(r)
15 }
16}
17
18// Based on John Leidegren's code at https://stackoverflow.com/a/54491783/376366
19func seedRandomizer() {
20 var b [8]byte
21 _, err := crypto_rand.Read(b[:])
22 if err != nil {
23 panic("cannot seed math/rand package with cryptographically secure random number generator")
24 }
25 math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
26}
Source: learning-go/random/random-integer-secure.go
The seedRandomizer
function gets 8 strongly random bytes using crypto/rand
's Read
function and uses that to seed math/rand
's random number generator. That makes it extremely difficult to predict or manipulate the numbers that will be generated, with no impact on the generator’s performace.
Random integer on a range
The Intn
function generates a random integer greater than or equal to 0 and less than an upper limit. To generate
integers in any given range, just shift the range by adding a padding to it.
For instance:
1package main
2
3import (
4 "encoding/binary"
5 "fmt"
6 crypto_rand "crypto/rand"
7 math_rand "math/rand"
8)
9
10func main() {
11 seedRandomizer()
12 for i := 0; i < 30; i++ {
13 fmt.Printf("%v ", randomInt(1, 10))
14 }
15 fmt.Printf("\n")
16}
17
18func randomInt(min, max int) int {
19 return min + math_rand.Intn(max - min + 1)
20}
21
22// Based on John Leidegren's code at https://stackoverflow.com/a/54491783/376366
23func seedRandomizer() {
24 var b [8]byte
25 _, err := crypto_rand.Read(b[:])
26 if err != nil {
27 panic("cannot seed math/rand package with cryptographically secure random number generator")
28 }
29 math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
30}
Source: learning-go/random/range.go
Notice how the randomInt
function adds min
to offset the lower limit of Intn
(which is 0) and how it sets the
upper limit to the number of possible values.
Sample output:
11 9 2 7 10 1 4 9 1 4 5 7 5 4 5 9 8 2 1 9 9 8 5 10 3 8 4 10 5 6
Random string
An easy way to generate a string of random characters is to generate random numbers that correspond to the desired characters in the ASCII chart.
For example this program will print strings composed of random lowercase letters:
1package main
2
3import (
4 "encoding/binary"
5 "fmt"
6 crypto_rand "crypto/rand"
7 math_rand "math/rand"
8)
9
10func main() {
11 seedRandomizer()
12 for i := 0; i < 3; i++ {
13 fmt.Println(randomString(20))
14 }
15}
16
17func randomInt(min, max int) int {
18 return min + math_rand.Intn(max - min + 1)
19}
20
21// Based on Flavio Copes's code at https://flaviocopes.com/go-random/
22func randomString(length int) string {
23 bytes := make([]byte, length)
24 for i := 0; i < length; i++ {
25 bytes[i] = byte(randomInt(97, 122))
26 }
27 return string(bytes)
28}
29
30// Based on John Leidegren's code at https://stackoverflow.com/a/54491783/376366
31func seedRandomizer() {
32 var b [8]byte
33 _, err := crypto_rand.Read(b[:])
34 if err != nil {
35 panic("cannot seed math/rand package with cryptographically secure random number generator")
36 }
37 math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
38}
Source: learning-go/random/strings.go
Sample output:
1awmckozkhqkullghvlga
2mhevzjivybulgfcocbnd
3sekxlbauktzloghkfecz
Random floating point numbers
The Float64
function will return pseudo-random floating point numbers greater than or equal to 0 and less than 1.
For example:
1package main
2
3import (
4 "encoding/binary"
5 "fmt"
6 crypto_rand "crypto/rand"
7 math_rand "math/rand"
8)
9
10func main() {
11 seedRandomizer()
12 for i := 0; i < 3; i++ {
13 r := math_rand.Float64()
14 fmt.Println(r)
15 }
16}
17
18// Based on John Leidegren's code at https://stackoverflow.com/a/54491783/376366
19func seedRandomizer() {
20 var b [8]byte
21 _, err := crypto_rand.Read(b[:])
22 if err != nil {
23 panic("cannot seed math/rand package with cryptographically secure random number generator")
24 }
25 math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
26}
Sample output:
10.5797031300913683
20.1786276285370312
30.3732865775928763
See the rand package documentation for other variations like:
Float32
returns a pseudo-random number as afloat32
.ExpFloat64
returns an exponentially distributedfloat64
.NormFloat64
returns a normally distributedfloat64
.
Takeaways
- The math/rand package has functions for generating random integer and floating-point numbers and doing related operations like permutations and shuffling.
- The crypto/rand package implements a cryptographically secure random number generator. It can be used for security-sensitive applications and it is handy for seeding a
math/rand
generator. See this article for a discussion of the trade-offs between themath
andcrypto
rand
packages. - Random numbers can easily be used to generate random values of more complex types.
comments powered by Disqus