分享一个go源码的均匀采样底层实现原理

// int31n也就是下面这个函数, 跟上面Int31n效果是一样的. 但是效率更高.算法不一样. 这个算法非常精彩,效率也更高.
// int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n).
// n must be > 0, but int31n does not check this; the caller must ensure it.
// int31n exists because Int31n is inefficient, but Go 1 compatibility
// requires that the stream of values produced by math/rand remain unchanged.
// int31n can thus only be used internally, by newly introduced APIs.
//
// For implementation details, see:
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
// https://lemire.me/blog/2016/06/30/fast-random-shuffling
func (r *Rand) int31n(n int32) int32 {
	v := r.Uint32()               //v是通过伪随机算法得到的一个0到2^32次幂之间的一个随机数.
	prod := uint64(v) * uint64(n) //得到prod是一个64位随机数.并且是n的倍数.显然因为v最大不超过2^32,所以prod的高32位:int32(prod >> 32)必小于n.
	low := uint32(prod)           //low是prod的低32位
	if low < uint32(n) {          //这个函数的算法核心重点:如果low大于等于n,那么我们prod的高32位可以看做1到 [prod/n]*n之间数的均匀采样得到的. 如果low小于n,那么就一定要让prod采样来自于一个n的倍数的区间.这就涉及下面thresh的计算.
		thresh := uint32(-n) % uint32(n) // 这个阈值运算的计算思想如下. 涉及到补码的知识. 道理如下: 首先我们v是取的1到2^32之间的随机数. uint32(-n)加上n就得到2^32, thresh是uint32(-n)比n的倍数多出来的数. 所以整个区间1到2^32次幂,我们需要扣除thresh这么多个数, 剩下来的就是n的倍数了.这就符合我们上面一行注释要求的需要一个n的倍数的区间来保证均匀分布性.
		for low < thresh {               //小于阈值就重新计算.直到保证抽样区间是n的倍数.
			v = r.Uint32()
			prod = uint64(v) * uint64(n)
			low = uint32(prod)
		}
	}
	return int32(prod >> 32)
}


posted on 2024-06-22 14:32  张博的博客  阅读(4)  评论(0编辑  收藏  举报

导航