4. 随机数生成原理与实现

在概率统计计算与随机模拟中,随机数是整个计算流程的起点。无论是估计圆周率、模拟排队系统,还是做蒙特卡洛积分,我们都需要大量服从特定分布的随机数。然而计算机本身是确定性的,它无法产生真正意义上的随机数,只能通过确定性算法生成看起来像随机的数列,这类数列被称为伪随机数。本节围绕伪随机数的生成原理、Python 中的实现方式以及均匀性检验展开。

一、伪随机数生成器的核心思想

伪随机数生成器(PRNG)本质上是一个确定性递推公式:给定一个初始种子(seed),后续每一个数都由前一个数通过固定运算得到。只要种子相同,生成的序列就完全相同,这既是缺点也是优点——缺点是可预测,优点是可复现,这对科学计算至关重要。

最经典的生成器是线性同余生成器(Linear Congruential Generator,LCG)。它的递推关系为:

X_{n+1} = (a * X_n + c) mod m
U_n = X_n / m

其中 m 为模数,a 为乘子,c 为增量,X_0 为种子。参数选择直接决定序列质量。例如取 m = 2^31、a = 1103515245、c = 12345,就是早期 C 语言常用的参数。LCG 计算极快,但存在明显缺陷:低位随机性差、周期有限、高维空间中点会落在少数超平面上(即所谓“晶格结构”),因此不适合高精度模拟。

现代 Python 的 random 模块默认使用梅森旋转算法(Mersenne Twister,MT19937)。它的状态是一个 624 个 32 位整数组成的数组,周期长达 2^19937 − 1,分布均匀性远优于 LCG,且经过大量统计检验。其代价是需要维护较大的内部状态,生成速度略慢于 LCG,但对绝大多数应用完全够用。

两类伪随机数生成器结构对比 线性同余生成器 LCG 种子 X₀ ×a + c mod m X₁ → U₁ X₂ → U₂ … 状态小、速度快,但周期有限 梅森旋转 MT19937 624 个 32 位 状态数组 旋转 + 扭曲 位运算 输出 U₁, U₂ … 周期 2¹⁹⁹³⁷−1,均匀性优良

二、用 Python 生成均匀随机数

Python 标准库 random 提供 random.random(),返回 [0, 1) 区间的浮点数;random.seed() 用于固定种子。若需要批量生成,random.uniform(a, b) 可映射到任意区间。示例:

import random
random.seed(42)
samples = [random.random() for _ in range(5)]
print(samples)
# 示例输出(固定种子下可复现):
# [0.6394267984578837, 0.025010755222666936, ...]

在数值计算场景中,更常用的是 numpy.random。它支持向量化批量生成,效率远高于循环调用。传统接口为 np.random.rand(n) 或 np.random.uniform(0, 1, n);新版本推荐使用 np.random.default_rng(seed) 创建生成器对象,再调用 rng.random(n),其底层为 PCG64,统计性质更好且线程安全。

import numpy as np
rng = np.random.default_rng(2024)
u = rng.random(10000)          # 10000 个 [0,1) 均匀随机数
print(u.mean(), u.min(), u.max())
# 示例结果约为 0.4998, 0.0001, 0.9999

注意 np.random.seed() 是全局状态,多个函数共享会互相干扰;而 default_rng 返回独立对象,更适合模块化代码和并行实验。

三、均匀性检验

生成随机数后,必须验证其是否“像”均匀分布。最直观的方法是直方图:把 [0,1) 分成 k 个等宽区间,统计落入各区间的频数,理想情况下各区间频数应接近 n/k。若某个区间明显偏高或偏低,说明分布有偏。

更严格的检验是卡方拟合优度检验。设将 [0,1) 分为 k 个区间,观测频数为 O_i,期望频数为 E_i = n/k,统计量:

χ² = Σ (O_i − E_i)² / E_i

在原假设“样本服从均匀分布”下,χ² 近似服从自由度为 k−1 的卡方分布。若计算得到的 χ² 大于临界值(例如显著性水平 α = 0.05 对应的分位数),则拒绝原假设,认为随机数均匀性不佳。示例:

from scipy import stats
u = rng.random(10000)
counts, _ = np.histogram(u, bins=10, range=(0, 1))
chi2, p = stats.chisquare(counts)
print(f"chi2={chi2:.2f}, p={p:.3f}")
# 若 p > 0.05,则不能拒绝均匀性假设

除卡方检验外,还可观察均值是否接近 0.5、方差是否接近 1/12 ≈ 0.0833,以及做游程检验、KS 检验等。实际工程中,单次检验通过不代表序列完美,通常需要多组种子、多种检验综合判断。

固定种子是复现实验的关键:论文、报告和调试中应显式记录种子值。但正式模拟时不要用固定种子反复“挑选”好看的结果,否则会引入选择偏差。
LCG 的低位比特随机性很差,若直接用 X_n mod 2 做随机位会得到明显规律。现代应用应优先使用 numpy.random.default_rng 或 random 模块,避免自行实现简易 LCG。

总结来说,伪随机数的质量取决于算法与参数,Python 的 random 与 numpy.random 已封装了成熟生成器;使用时应固定种子保证可复现,并通过直方图与卡方检验确认均匀性,为后续的随机模拟打下可靠基础。