该博文为补档,原文作于 2022 年,现已遗失。由站主根据回忆补档并予以删改,或与原文略有出入。
在大多数的计算机语言中,随机数函数生成的都是均匀分布的随机数。然而在现实生活中,几乎所有的事物的属性分布都符合一种平均值近处密集,平均值远处稀疏的分布,也就是正态分布。那么,当我们要用随机生成模拟现实中某个事物的属性时(如随机生成 NPC 的身高),我们就需要一种方法,生成符合正态分布的随机变量。
关于正态分布,此处不再赘述,可以参考这篇文章:数学基础 | 什么是正态分布?
Javascript 中的 Math.random()
随机数函数生成的是均匀分布于 [0,1) 的随机变量,转化成正态分布变量有两个方法.第一种使用 Box-Muller 算法,生成成对的正态分布随机变量;第二种借助中心极限定理,通过对独立同分布随机变量取均值,得到正态分布随机变量。
Box-Muller 算法
算法概述
Box-Muller 算法可以将两个 [0,1] 上的均匀分布随机变量转化成服从 N(0,12) 的标准正态分布随机变量,公式如:
X=−2lnucos2πv
Y=−2lnusin2πv
其中 u,v 是 (0,1] 上的两个均匀分布随机数。
证明
Box-Muller 算法本质上是生成二维正态分布样本点,因此它的入参和出参都是成对出现的。二维正态分布可以看作在两个维度上独立的两个正态分布,其概率密度函数可以写成两个一维正态分布的乘积。
设有相互独立的随机变量 X,Y∼N(0,12),其概率密度函数为
p(X)=2π1e−2X2,p(Y)=2π1e−2Y2
由于 X,Y 相互独立,所以它们的联合概率密度满足
p(X,Y)=p(X)p(Y)=2π1e−2X2+Y2
将 X,Y 变换为极坐标,即令 X=Rcosθ,Y=Rsinθ,则有
∬−∞∞2π1e−2X2+Y2dXdY=∬−∞∞2π1e−2R2RdRdθ=1
也即
∫−∞∞∫−∞∞2π1e−2R2RdRdθ=1
角度分量 θ 是在 [0,2π] 上均匀分布,这一点比较好理解。再看半径分量 R,设 R 的分布函数为 PR,则
PR(R<t)=∫02π∫0t2π1e−2R2RdRdθ=1−e−2t2
令
FR(t)=1−e−2t2
其反函数
R=FR−1(t)=−2ln(1−t)
也即当 t(也就是 1−t)服从 [0,1] 上的均匀分布时,R 的概率密度函数为 FR(t),因此可以选取两个服从 [0,1] 上均匀分布的随机变量 u,v,使得
θ=2πv,R=−2lnu
代入 X=Rcosθ,Y=Rsinθ 即可得到原公式
X=−2lnucos2πv
Y=−2lnusin2πv
代码实现
依据公式直接模拟即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
function NormalDistbRandom(mean, stdDev){ let u, v, std; u = 1 - Math.random(); v = 1 - Math.random(); std[0] = Math.sqrt(-2 * (Math.log(u)/Math.log(Math.E))) * Math.cos(2 * Math.PI * v); std[1] = Math.sqrt(-2 * (Math.log(u)/Math.log(Math.E))) * Math.sin(2 * Math.PI * v); return [std[0] * stdDev + mean, std[1] * stdDev + mean]; }
|
但是,这个算法的实现需要用到三角函数,时间效率比较低,所以我们有更高效的算法如下。
中心极限定理
中心极限定理指出,对于相互独立、服从相同分布且期望与方差有限的随机变量,即使原始变量本身不是正态分布,样本均值的抽样分布也趋向于正态分布。
定理概述
设随机变量 X1,X2,...,Xn 独立同分布,且具有有限的期望与方差 E(Xi)=μ,D(Xi)=σ2=0,(i=1,2,3,...,n),记 Xˉ=1/n∑i=1nXi,ζn=σ/nXˉ−μ,则有 Xˉ∼N(μ,nσ2),n→∞limP(ζn<=z)=Φ(z),其中 Φ(z) 是标准正态分布的分布函数。关于此定理的证明请见 中心极限定理 - 维基百科
代码实现
我们先将定理中得到的正态分布转换成标准正态分布。已知有:
Xˉ∼N(μ,nσ2)
其中 μ=E(Xi),σ2=D(Xi)=0,(i=1,2,3,...,n)
将所有样本减去 μ 得到:
Xˉ−μ∼N(0,nσ2)
将所有样本乘以 σn 得到:
σn(Xˉ−μ)∼N(0,1)
上下同乘 n 得到:
σn∑i=1nXi−nμ∼N(0,1)
Javascript 中的 Math.random()
随机数函数生成的是均匀分布于 [0,1) 的随机变量,其期望 μ=21,方差 σ2=121,将其代入上式得到:
12n∑i=1nXi−2n∼N(0,1)
注意到当 n=12 时可以消去分母中的 12,式中常数只有整数,也即:
i=1∑12Xi−6∼N(0,1)
很大程度上简化了实现步骤,节省时间开销。据此写出对应的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function NormalDistbRandom(mean, stdDev){ let std = 0; for(let i = 0; i < 12; i ++) { std += Math.random(); } std -= 6; return std * stdDev + mean; }
|