任何有编程经验的人都知道计算机是确定性机器。如果您提供相同的输入,您将始终获得相同的输出。这就是为什么让计算机偶然产生一些东西比它看起来更棘手的原因。
从密码学到赌博、生成算法、视频游戏等等,计算机都使用随机数。然而,计算机本质上不可能是随机的。取而代之的是,程序员依赖于伪随机数生成器(PRNG)。这些只是一类算法,它们以编程方式从称为种子的给定起始值生成新的随机数。
这些算法也不是没有自己的局限性。因为随机数是以编程方式生成的,所以如果有人能够识别种子值和您正在使用的PRNG算法,他们就能够预测序列中的下一个随机数。这将允许攻击者破解加密、预测序列中的下一张扑克牌、在视频游戏中作弊等。
尽管存在这种担忧,PRNG在涉及建模和模拟的情况下还是非常有用的,因为它允许您通过使用相同的种子初始化随机数生成器来“重放”一系列随机事件。
在随机数的随机性很关键的情况下,我们使用“真正的”随机数生成器(TRNG)。与具有任意种子值的PRNG不同,TRNG从其环境/外部数据中选取种子值。
我们只需要挑选一个攻击者无法预测的种子。然后,该种子值将被传递到类似于PRNG的算法中,该算法将生成要使用的随机数。
用例通常将规定PRNG是否足够,或者是否需要“真正的”RNG。无论如何,重要的是要了解这两种方法之间的实际差异。
PRNG比TRNG更快,并且它们的决定论在您想要重放一系列“随机”事件的情况下非常有用。此外,一些PRNG本质上是周期性的,但是具有正确初始化参数的现代PRNG具有足够长的周期,这不是主要问题。相反,TRNG比PRNG慢,是非确定性的,并且不是周期性的。
让我们来看看如何实现一个简单的PRNG。我们将实现一个称为线性同余生成器(LCG)算法的变体。LCG以前是最常用和最受研究的PRNG之一(更多信息)。
LCG上的Wikipedia页面记录了一些常用的模数、乘数和增量值。对于要使用的最佳值没有一致意见,因此不同实现的值不同。
我们必须注意我们为这些参数使用的值。选择错误的值可能会创建太短的周期,这将使我们的随机数生成器无法使用。
在下图中,您可以看到对参数的微小更改会极大地影响周期长度。
对于我们的实现,我们将使用以前的C语言标准(C90/C99/ANSI C、C和C11)中记录的值。
无论您选择哪种PRNG算法,都应该导致随机数的均匀分布和足够长的周期。
将模数更改为6似乎是合理的,但这将创建一个太短而无法使用的周期。对于我们的参数,我们需要坚持使用经过精心选择和测试的值。
看看结果,我们可以看到,它确实是一个均匀的数值分布。
接下来,让我们考虑生成落在某个范围内的随机数。再说一次,我们不应该在没有充分了解它是如何影响周期的情况下改变我们的参数。
相反,我们应该将PRNG的结果映射到所需范围内的值。
如果您对更现代的PRNG感兴趣,我建议您探索Mersenne-Twister方法。它是目前最流行的PRNG算法,目前在Python(Numpy)、Ruby、PHP、R和C++中使用。