Int31n()源码解析 ---- math/rand/rand.go

    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() 函数的说明

        在我的环境中, 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值