05.10 生成随机数

随机数的生成是计算机科学的一个研究领域,同时也是一种艺术。这是因为计算机是纯粹的逻辑机器,所以使用计算机生成随机数异常困难!

你可以用 math/rand 包来生成随机数。开始生成随机数之前首先需要一个种子,种子用于整个过程的初始化,它相当重要。因为如果你每次都用同样的种子初始化,那么就总是会得到相同的随机数序列。这意味着每个人都可以重新生成同一个序列,那这个序列就根本不能再算是随机的了!

我们将用来生成随机数的程序名为 randomNumbers.go,下面分成四个部分进行介绍。这个程序需要几个参数,分别是生成随机数的下限、生成随机数的上限、生成随机数的数量。如果你还用了第四个命令参数,那么程序会将它作为随机数生成器的种子。在测试的时候,你就可以再次使用这个参数生成相同的数列。

程序的第一部分如下所示:

package main

import (
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "time"
)

func random(min, max int) int {
    return rand.Intn(max-min) + min
}

random() 函数完成了所有的工作,它通过根据指定的范围调用 rand.Intn() 生成随机数。

这个命令行程序的第二部分如下:

func main() {
    MIN := 0
    MAX := 100
    TOTAL := 100
    SEED := time.Now().Unix()

    arguments := os.Args

这一部分对程序中将会用到的变量进行了初始化。

randomNumbers.go 的第三部分包含如下的 Go 代码:

    switch len(arguments) {
    case 2:
        fmt.Println("Usage: ./randomNumbers MIN MAX TOTAL SEED")
        MIN, _ = strconv.Atoi(arguments[1])
        MAX = MIN + 100
    case 3:
        fmt.Println("Usage: ./randomNumbers MIN MAX TOTAL SEED")
        MIN, _ = strconv.Atoi(arguments[1])
        MAX, _ = strconv.Atoi(arguments[2])
    case 4:
        fmt.Println("Usage: ./randomNumbers MIN MAX TOTAL SEED")
        MIN, _ = strconv.Atoi(arguments[1])
        MAX, _ = strconv.Atoi(arguments[2])
        TOTAL, _ = strconv.Atoi(arguments[3])
    case 5:
        MIN, _ = strconv.Atoi(arguments[1])
        MAX, _ = strconv.Atoi(arguments[2])
        TOTAL, _ = strconv.Atoi(arguments[3])
        SEED, _ = strconv.ParseInt(arguments[4], 10, 64)
    default:
        fmt.Println("Using default values!")
    }

switch 代码块背后的逻辑相对简单:根据命令行参数的数量决定程序中的参数是使用缺省的默认值还是使用用户提供的值。为了简化程序,strconv.Atoi()strconv.ParseInt() 函数返回的 error 参数使用下划线字符接收,然后被忽略。如果是商业程序就千万不能忽略 strconv.Atoi()strconv.ParseInt() 函数返回的 error 参数!

最后,使用 strconv.ParseInt()SEED 变量赋新的值是因为 rand.Seed() 函数要求的参数类型是 int64strconv.ParseInt() 的第一个参数是要解析的字符串,第二个参数是输出数的基数,第三个参数是输出数的位数。由于我们想要生成的是一个 64 位的十进制整数,所以使用 10 作为基数,使用 64 作为位数。注意,如果你想解析无符号的整数的话应该使用 strconv.ParseUint() 函数代替。

randomNumbers.go 的最后一部分是如下的 Go 代码:

    rand.Seed(SEED)
    for i := 0; i < TOTAL; i++ {
        myrand := random(MIN, MAX)
        fmt.Print(myrand)
        fmt.Print(" ")
    }
    fmt.Println()
}

除了使用 Unix 时间戳作为随机数生成器的种子,你还可以使用 /dev/random 这个系统设备。你可以在第 8 章“Go Unix系统编程”中了解 /dev/random 的相关内容。

执行 randomNumbers.go 将会生成如下输出:

$ go run randomNumbers.go
75 69 15 75 62 67 64 8 73 1 83 92 7 34 8 70 22 58 38 8 54 91 65 1 50 76 5 82 61 90 10 38 40 63 6 28 51 54 49 27 52 92 76 35 44 9 66 76 90 10 29 22 20 83 33 92 80 50 62 26 19 45 56 75 40 30 97 23 87 10 43 11 42 65 80 82 25 53 27 51 99 88 53 36 37 73 52 61 4 81 71 57 30 72 51 55 62 63 79
$ go run randomNumbers.go 1 3 2
Usage: ./randomNumbers MIN MAX TOTAL SEED
1 1 
$ go run randomNumbers.go 1 3 2
Usage: ./randomNumbers MIN MAX TOTAL SEED
2 2
$ go run randomNumbers.go 1 5 10 10
3 1 4 4 1 1 4 4 4 3
$ go run randomNumbers.go 1 5 10 10
3 1 4 4 1 1 4 4 4 3

如果你对随机数生成真的很有兴趣,那么你应该先读一下 Donald E.Knuth 写的 The Art of Computer Programming (Addison-Wesley Professional, 2011) 的第二卷。

如果需要用 Go 生成更安全的随机数的话,你可以使用 crypto/rand 包。这个包中实现了满足密码学安全的伪随机数生成器。你可以在 https://golang.org/pkg/crypto/rand/ 文档页面了解更多关于 crypto/rand 包的信息。

Last updated