golang实践-随机数的那点事儿
来源:互联网 发布:平面设计和淘宝美工 编辑:程序博客网 时间:2024/06/05 21:57
序:不同种子竟然可以得到相同的结果
我们用随机数,是期望每次得到的结果不同,因此我们传递不同的seed,来获取。但事实上,即使种子不同,我们也可能会得到重复、且有规律的取值。运行以下代码看看:
func main() { now := time.Now() after := now.Add(time.Duration((1 << 31) - 1)) fmt.Printf("seed:%v,result=%v\n",now.Format("2006:01:02 15:04:05"),randPrint(now.UnixNano())) fmt.Printf("seed:%v,result=%v\n",after.Format("2006:01:02 15:04:05"),randPrint(after.UnixNano()))}func randPrint(seed int64) []int { var ( number = 10 result = make([]int, number) ) rand.Seed(seed) for i := 0; i < number; i++ { result[i]=rand.Intn(100) } return result}
不论你在什么时候,运行的两行结果肯定是相通的。我运行时,得到的输出:
seed:2017:02:24 11:48:32,result=[61 82 94 81 7 37 36 21 13 93]seed:2017:02:24 11:48:34,result=[61 82 94 81 7 37 36 21 13 93]
此外,如果seed=math.MinInt32、-2058001336、0、89482311,他们的随机结果也会相同。
我擦!咋个了
math.rand 解读
针对这个问题,立马看了代码,很容易发现,种子的问题。代码中,核心的初始化如下:
//rng.go,rand初始化的相关代码func (rng *rngSource) Seed(seed int64) { rng.tap = 0 rng.feed = _LEN - _TAP seed = seed % _M //<--对_M = (1 << 31) - 1求模 if seed < 0 { //转为正整数 seed += _M } if seed == 0 { seed = 89482311 //0无法进行后续运算,seedrand(x)里对x进行了整除 } x := int32(seed) for i := -20; i < _LEN; i++ { //对缓冲池rng.vec初始化,后续将从rng.vec取值 x = seedrand(x) if i >= 0 { var u int64 u = int64(x) << 40 x = seedrand(x) u ^= int64(x) << 20 x = seedrand(x) u ^= int64(x) u ^= rng_cooked[i] rng.vec[i] = u } }}
由于内部基于math.MaxInt32(1 << 31) - 1)求模,因此会有大量的同余整数。当种子为math.MinInt32、-2058001336、89482311的时候,余数都是89482311;0,则是被定向到了89482311,属于例外。
问题的原因这下也清楚了,由于int64远大于int32,所以传入的seed很容易造成rngSource在初始化时,出现重复的.
在看代码,会发现规律都相同。每次Int/intn/Uint32/Int31,其实都是调用Int63。该方法从池中获取内部两个索引指向的缓存数值相加(同时会更新其中一条,下次使用)。
//rng.go,rand初始化的相关代码// Uint64 returns a non-negative pseudo-random 64-bit integer as an uint64.func (rng *rngSource) Uint64() uint64 { rng.tap-- if rng.tap < 0 { rng.tap += _LEN } rng.feed-- if rng.feed < 0 { rng.feed += _LEN } x := rng.vec[rng.feed] + rng.vec[rng.tap] rng.vec[rng.feed] = x return uint64(x)}
到此,我们可以非常明确:
相同种子,每次结果必然相同,这就是伪随机数。
此外,尽管算法方面有改进,但即使种子不同,但很可能出现同样规律的结果。比如:
- 前面提到的0,math.MinInt32
- 还可以很快发现的 2,15764469
- ……
真正的随机数
go语言中,为密码提供了另外的随机数获取途径,那就是"crypto/rand"
包。代码中注释非常明确说明,数据源来自于哪里。这些数据来自于每台机器非常清晰:
// Package rand implements a cryptographically secure// pseudorandom number generator.package randimport "io"// Reader is a global, shared instance of a cryptographically// strong pseudo-random generator.//// On Linux, Reader uses getrandom(2) if available, /dev/urandom otherwise.// On OpenBSD, Reader uses getentropy(2).// On other Unix-like systems, Reader reads from /dev/urandom.// On Windows systems, Reader uses the CryptGenRandom API.var Reader io.Reader
以Linux为例,优先调用getrandom(2),其实就是/dev/random
优先。与/dev/urandom两个文件,他们产生随机数的原理其实是差不多的,本质相同:都是利用当前系统的熵池来计算出固定一定数量的随机比特,然后将这些比特作为字节流返回。
熵池就是当前系统的环境噪音,熵指的是一个系统的混乱程度,系统噪音可以通过很多参数来评估,如内存的使用,文件的使用量,不同类型的进程数量等等。
由于环境噪声好无规律,更不会用种子来初始化,因此每次访问,其逻辑都是不可预测的。不过使用的时候要注意,尽管没有规律,但转换为数字或字符串后,依然可能会重复。
使用的时候非常简单,自己先初始化一个[]byte,然后调用API就好了,之后要注意自己转换。比如要随机生成一个int32:
var x uint32 binary.Read(crand.Reader,binary.BigEndian,&x) fmt.Println(x)
代码非常简单。但要注意,通过这种方式,要比math.rand慢10来倍。
- golang实践-随机数的那点事儿
- VSS的那点事儿
- 就业的那点事儿
- 程序员的那点事儿
- Copy的那点事儿~
- String 的那点事儿
- RecyclerView的那点事儿
- CardView的那点事儿
- SurfaceView的那点事儿
- OC 的那点事儿
- C 的那点事儿
- 预处理的那点事儿
- 工作的那点事儿
- 加班的那点事儿
- .cn域名的那点事儿
- 关于Contacts的那点事儿
- 关于Contacts的那点事儿(续)
- Switch和Enum的那点事儿
- 开发问题汇总
- <spark>pairRDD
- SAP HANA通过SQL语句更新HANA参数
- 空间换时间,轻松提高性能100倍
- 【codeforces 776D】The Door Problem
- golang实践-随机数的那点事儿
- jquery在html有效在jsp无效的问题
- poj3233(矩阵快速幂)
- sqlChemy相关知识点总结(增删改查)
- Maven的pom.xml文件详解------Build Settings
- poj3070 矩阵快速幂
- Generator-Mybatis自动生成代码
- jvisualvm远程监控java虚拟机
- sql注入原理