◆◆ ◆
前言
“So much of life, it seems to me, is determined by pure randomness.” – Sidney Poitier
在之前的博客中,或多或少都提到过一些随机、伪随机、随机数等等,但基本上只是直接使用,没有探寻背后的一些原理,刚好最近偶然看到python标准库中如何生成服从正态分布随机数的源码,所以本文就简单聊聊如何生成正态分布
◆◆ ◆
知识回顾
为了防止你对接下来的内容一头雾水,我觉得还是有必要回顾一下我们曾经学过的高数和概率统计知识
均匀分布
不过这些约束怎么来的本文就暂不讨论了
我们以Java中Random的实现为例,为了便于理解,这里对代码进行了部分调整,只截取了相关的片段
//这两个东西在本文后面讲正态分布的时候会涉及到
private double nextNextGaussian;
private boolean haveNextNextGaussian = false;
//随机数序列生成器种子
private final AtomicLong seed;
//线性同余发生器乘数a
private final static long multiplier = 0x5DEECE66DL;
//线性同余发生器加数c
private final static long addend = 0xBL;
//线性同余发生器模数m
private final static long mask = (1L << 48) - 1;
public Random(long seed) {
this.seed = new AtomicLong(0L);
setSeed(seed);
}
synchronized public void setSeed(long seed) {
//实现线性同余算法
seed = (seed ^ multiplier) & mask;
//atomic set具有写入(分配)volatile 变量的内存效果(即具有内存可见性)
this.seed.set(seed);
//暂且忽略。。
haveNextNextGaussian = false;
}
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
//ompareAndSet 如果当前值==预期值,则以原子方式将该值设置为给定的更新值(利用了CPU的硬件原语CAS指令)
do {
//atomic get具有读取volatile 变量的内存效果
oldseed = seed.get();
//实现线性同余算法
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
//截取bits位整型
return (int)(nextseed >>> (48 - bits));
}
有人可能会有疑惑,这个代码中的实现nextseed = (oldseed * multiplier + addend) & mask好像和递推公式不一样啊?那个模运算为什么变成了与运算?
注意,x&[(1L << 48)–1]与x(mod 2^48)是等价的。为什么呢?从二进制的角度来考虑这个问题就很清楚了。一个数x除以2^n,在二进制中相当于将x右移n位,商和余数分别在小数点左侧和右侧。如23/8=2,23%8=7,23的二进制表示为10111,除以2^3=8相当于右移3位,得到10.111,左侧为商10也就是2,右侧为余数111也就是7。也就是说如果一个数