按下一个按钮,一个数字出现,人们就假定它是随机的。值得问的问题是:是什么产生了它——因为两个答案里,一个是可验证的,另一个不是。
真正的随机性从何而来
真正的随机性来自不由先前状态决定的物理过程:
- 放射性衰变——原子衰变的确切时刻无法预测
- 热噪声——电阻中随机的电压波动
- 大气噪声——闪电活动的背景静电
- 量子测量——真正不确定的结果
这些不是随机性的近似。它们就是随机的,其意义是:关于过去的任何知识都无法决定结果。
计算机改用什么
计算机无法直接观测其中任何一种,所以硬件生成器对它们进行采样。原始字节来自 /dev/urandom 或等效的操作系统来源,而后者本身由物理过程播种。这才是真随机数生成器。
更常见得多的情况是伪随机数生成器:一种确定性算法,产生的序列只是看起来随机。
next = (previous × multiplier + increment) mod modulus
关键性质并不微妙。如果你知道算法和种子,你就能算出这个序列将来会产生每一个值。不是近似——是精确。
这就是为什么几乎每个网站都在用的 Math.random() 不适用于任何要紧的事。它的输出由内部状态决定。从观测到的输出中恢复该状态,每一次未来的抽取就都变得可预测。
最常被引用的例子是,一个线性同余生成器的完整状态可以从少量连续输出中恢复。Python 的默认生成器 Mersenne Twister 需要 624 个输出才能重建——而 624 这个数字你几秒钟就能达到。
Xorshift 及类似的快速生成器有同样性质的另一种形式:统计质量极好,但只要拿到状态就完全可以预测。
真正重要的中间地带
这是大多数解释略过的部分,也是实践中决定答案的部分。

现代网络平台每次调用并不使用原始的 /dev/urandom。它们使用加密安全伪随机数生成器——一种确定性算法,但由硬件熵播种,并被设计成观测输出不会让你恢复状态。
这个区别值得讲准确,因为两者常被混为一谈:
| 硬件真随机数生成器 | CSPRNG | 普通伪随机数生成器 | |
|---|---|---|---|
| 基于物理熵 | 是 | 是 | 否 |
| 给定状态后是确定性的 | 否 | 是 | 是 |
| 输出会暴露状态 | 否 | 否 | 是 |
| 适合用于抽奖 | 是 | 是 | 否 |
CSPRNG 是确定性的,其含义与你使用的每一款软件都是确定性的完全一样。让它可接受的原因在于,状态无法从它已经输出的内容中恢复。
浏览器正是通过 crypto.getRandomValues() 暴露这一点。它就是 RandomSelect 抽取所用的来源。
公平性是第二个问题
即使来源很好,均匀性也很容易搞错。一个 d6 很直接:六个同等可能的值。一个 d7 不是,而“从 40 个名字里挑 1 个”也不是。
朴素的做法——把 0 到 1 的随机浮点数缩放到区间上——会让低端产生偏倚。有 40 个名字时,第一个出现的频率会比最后一个略高,因为一些随机值落在最后一个桶的末尾之外,并被不均等地丢弃。
正确的做法是拒绝采样:抽一次,如果结果落在不完整的最后一个桶里,就丢弃它再抽一次。分布会变得完全均匀。
这容易被忽视,因为偏倚很小。实践中,实现会拒绝超出范围的值并重新抽取,于是每个条目都恰好有 1/N 的概率——包括在 40 个名字的抽签中,缩放浮点数的实现会给第一个名字略高一点的概率。
同样的技术也出现在短 ID 生成器里,代码中的注释写明了接受率和由此产生的大约千万分之二的偏倚。小到无关紧要,也小到一个粗心的实现会不被察觉——而这恰恰是测量它的意义所在。
这在实践中意味着什么
为了选午餐,这些都不重要。任何生成器都能产生一顿可以接受的午餐。
对于抽奖、员工分配,或任何附带声誉的事,问题在于输出是否本可以被预判。用 Math.random(),原则上可以。用 crypto.getRandomValues(),不可以——这就是“我们说它是随机的”和“机制使它如此”之间的差别。
还有一个相关且常被混淆的关于动画的点。在旋转的转盘上,结果在转盘开始转动之前就已固定;动画朝它播放。有些读者把这当作操纵的证据。事实恰恰相反:因为结果已经确定,无法被时机影响,所以没有任何输入可供任何人利用。视觉只是在一个已经固定的值之上演的戏。
自己测试一下
一个不需要代码的小练习:掷骰子二十次,把结果记下来。然后找连续相同的串。
你会找到。连续三个六大约每五十次掷骰出现一次。这不是骰子灌铅的迹象——这就是一个公平过程在短样本中看起来的样子。
然后掷一百次,数每个面。差距会收窄。没有人正在收敛,因为从来就没有什么赤字需要弥补:这就是独立性的含义,只要你观察得足够仔细。
在哪里了解更多
本站的公平性页面会带你走一遍实际实现,包括拒绝采样这一步。值得读一遍,尤其是当你在为一次会被质疑的决定挑选工具时。
实用的总结很短:一个使用 crypto.getRandomValues() 并拒绝超出范围抽取的工具,做的是对的事,而你不需要相信它的话——你可以改为推理机制。