0
点赞
收藏
分享

微信扫一扫

Int31n() ---- math/rand/rand.go

Brose 2022-06-14 阅读 26

        Int31n 用于返回一个类型为 int32 的伪随机非负整数, 其值属于左闭右开区间 [0, n), 其中 n 即调用该函数时传入的参数.

        在学习该段代码前, 先看一段 C 语言代码:

#include <stdlib.h>
#include <stdio.h>

int main()
{
int small = 0;
int big = 0;

for (int i = 0; i != 200000; ++i)
{
int r = rand() % 31768;

if (r <= 999) ++small;
else if (r >= 30768) ++big;
}

// 在电脑上某一次运行的结果
// 实际上, 由于没有设置随机种子, 每一次运行都会是这个结果
// small: 12227 - big: 6025
printf("small: %d - big: %d", small, big);

return 0;
}

        类似于Int31n(), 这段代码是随机获取一个 [0, 31768) 范围内的整数, 然后循环20万次, 比较前1000个数和后1000个数出现的次数, 理论上期望两者次数应当是 1:1 的比例, 然而实际上前者比后者多出了一倍, 为什么会这样呢? 来看一下 rand() 函数的说明


Int31n() ---- math/rand/rand.go_源码

        在我的环境中, RAND_MAX 是 32767, 这意味着当随机值位于 [31768, 32767) 时, 会产生 [0, 999] 的结果, 但是大于 999 的数值不会再产生了, 这样一来, 前1000个数出现的可能性就会比其它数的可能性要高, 概率就不够平均了.

        了解了上面的情况, 下面再来看一下 GO 里的实现

func (r *Rand) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
if n&(n-1) == 0 { // n is power of two, can mask
return r.Int31() & (n - 1)
}
// 在所有可能的随机数中取一个最大整数, 使得进行取余操作时, 所有余数出现的次数是相同的
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := r.Int31()
for v > max {
// 此时会导致对应的余数出现的次数比其它数多, 所以舍弃掉该随机值, 重新随机一次
v = r.Int31()
}
return v % n
}

说来惭愧, 由于大脑 CPU 过于陈旧, 虽然知道对于 max 的赋值是取一个最大整数, 但是不知道为什么这样操作可以取到, 思考半天, 才理解了. 理解过程如下:

        为了保证所有余数出现的次数相同, 这个最大整数一定是 n 的整数倍减1, 比如说 n = 5, 那么这个整数一定是 4, 9, 14, 19......, 对应的余数 [0, 4] 次数分别是 1, 2, 3, 4......, 因此设倍数为 m, 所有随机数的最大值为 max, 可得如下不等式:

m * n - 1 <= max

转换过后可得

m <= (max + 1) / n,

那么 m 的最大值即为 ((max + 1) - (max + 1) % n) / n, 可知

最大整数 = ((max + 1) - (max + 1) % n) / n * n - 1

  = (max + 1) - (max + 1) % n - 1

  = max - (max + 1) % n

1 << 31后的二进制值为 10000000000000000000000000000000, 减去1为01111111111111111111111111111111, 即 int32 类型的最大值, 因此 (1 << 31) - 1 为 Int31()所能取到的最大随机数, 而 (1 << 31) 即为该最大随机数加1.

回头再来看为什么会有这句话

if n&(n-1) == 0 { // n is power of two, can mask
return r.Int31() & (n - 1)
}

        1 << n 位的值就是2的 n 次方的值, 比如说 1<<3位, 得到的二进制为 1000, 其值为8, 然后2的3次方其值也是8, 所以 1<<31位其实就是2的31次方, 那么 2的31次方 / 2的n次方 = 2的(31 - n)次方, 是没有余数的, 因此max - (max + 1) % n等价于 max, 所以取值范围就是整个 Int31() 的范围.

举报

相关推荐

0 条评论