rand()
尽管使用种子 via ,但通常不赞成使用 of srand()
。为什么会这样?有什么更好的选择?
7 回答
这个故事有两个部分。
首先,rand
是一个伪随机数生成器。这意味着它取决于种子。对于给定的种子,它将始终给出相同的序列(假设相同的实现)。这使得它不适合某些高度关注安全性的应用程序。但这并不特定于rand
. 这是任何伪随机生成器的问题。并且肯定有很多类别的问题可以接受伪随机生成器。真正的随机生成器有其自身的问题(效率、实现、熵),因此对于与安全无关的问题,通常使用伪随机生成器。
因此,您分析了您的问题并得出结论,伪随机生成器是解决方案。在这里,我们遇到了 C 随机库(包括rand
和srand
)的真正问题,这些问题特定于它并使其过时(又名:你永远不应该使用的原因rand
和 C 随机库)。
一个问题是它有一个全局状态(由 设置
srand
)。这使得不可能同时使用多个随机引擎。它还使多线程任务变得非常复杂。它最明显的问题是它缺少一个分发引擎:
rand
给你一个间隔的数字[0 RAND_MAX]
。它在这个区间内是均匀的,这意味着这个区间内的每个数字都有相同的概率出现。但大多数情况下,您需要一个特定间隔内的随机数。比方说[0, 1017]
。一个常用(和幼稚)的公式是rand() % 1018
. 但问题在于,除非RAND_MAX
是精确的倍数,否则1018
不会得到均匀分布。另一个问题是实施的质量
rand
。这里还有其他答案比我能更好地详细说明这一点,所以请阅读它们。
在现代 C++ 中,您绝对应该使用 C++ 库,<random>
其中包含多个随机定义明确的引擎以及整数和浮点类型的各种分布。
rand()
这里的答案都没有解释坏的真正原因。
rand()
是一个伪随机数生成器 (PRNG),但这并不意味着它一定是坏的。实际上,有非常好的 PRNG,它们在统计上很难或不可能与真正的随机数区分开来。
rand()
是完全实现定义的,但从历史上看,它是作为线性同余生成器 (LCG)实现的,这通常是一种快速但臭名昭著的 PRNG 类。这些生成器的低位具有比高位低得多的统计随机性,并且生成的数字可以产生可见的晶格和/或平面结构(最好的例子是著名的RANDU PRNG)。一些实现尝试通过将位右移一个预定义的量来减少低位问题,但是这种解决方案也减少了输出的范围。
尽管如此,还是有一些优秀的 LCG 的显着例子,例如 L'Ecuyer 的 64 位和 128 位乘法线性同余发生器,在不同大小和良好晶格结构的线性同余发生器表中提出,Pierre L'Ecuyer,1999 年。
一般的经验法则是不要信任rand()
,使用您自己的符合您的需求和使用要求的伪随机数生成器。
rand
/的坏处srand
是rand
——</p>
- 对其生成的数字序列使用未指定的算法,但
- 允许将该算法初始化
srand
为可重复的“随机性”。
这两点结合在一起,阻碍了实现改进实现的能力rand
(例如,使用加密随机数生成器 [RNG] 或其他“更好”的算法来生成伪随机数)。例如,JavaScriptMath.random
和 FreeBSDarc4random
没有这个问题,因为它们不允许应用程序为可重复的“随机性”播种它们——正是由于这个原因,V8 JavaScript 引擎能够将其Math.random
实现更改为xorshift128+
while的变体保持向后兼容性。(另一方面,让应用程序提供额外的数据来补充“随机性”,如 中的BCryptGenRandom
,问题较小;但即便如此,
还:
- 和 的算法和播种过程未指定这一事实
rand
意味着srand
即使在rand
/srand
实现之间、同一标准库的版本之间、操作系统之间等之间也不能保证可重现的“随机性”。 - 如果
srand
在 is 之前未调用,rand
则rand
其行为类似于srand(1)
第一次调用。在实践中,这意味着它rand
只能实现为伪随机数生成器 (PRNG) 而不是非确定性 RNG,并且rand
无论应用程序是否调用,PRNG 算法在给定实现中都不会有所不同srand
。
编辑(2020 年 7 月 8 日):
还有一件更重要的事情是不好的rand
和srand
。这些函数的 C 标准中没有任何内容指定由所传递的“伪随机数”rand
必须遵循的特定分布,包括均匀分布,甚至是近似于均匀分布的分布。将此与 C++uniform_int_distribution
和uniform_real_distribution
类以及 C++ 指定的特定伪随机生成器算法(例如linear_congruential_engine
和mt19937
.
编辑(2020 年 12 月 12 日开始):
rand
关于and的另一个坏处是srand
:srand
种子只能像unsigned
. unsigned
必须至少为 16 位,并且在大多数主流 C 实现中,unsigned
是16 位或 32 位,具体取决于实现的数据模型(尤其不是 64 位,即使 C 实现采用 64 位数据模型)。因此,通过这种方式可以选择不超过 2^N 个不同的数字序列(其中 N 是 an 中的位数unsigned
),即使实现的底层算法rand
可以产生比这更多的不同序列(例如,2^128甚至是 C++ 中的 2^19937 mt19937
)。
首先,srand()
没有得到种子,它设置了种子。播种是使用任何伪随机数生成器 (PRNG) 的一部分。当播种时,PRNG 从该种子产生的数字序列是严格确定的,因为(大多数?)计算机无法生成真正的随机数。更改您的 PRNG 不会阻止序列从种子中可重复,事实上,这是一件好事,因为产生相同的伪随机数序列的能力通常很有用。
那么,如果所有 PRNG 都共享这个特性,rand()
为什么会被rand()
认为是坏的呢?好吧,它归结为伪随机的“伪”部分。我们知道 PRNG 不可能是真正随机的,但我们希望它的行为尽可能接近真正的随机数生成器,并且可以应用各种测试来检查 PRNG 序列与真正的随机序列的相似程度. 尽管标准未指定其实现,但rand()
在每个常用的编译器中,都使用了一种非常古老的生成方法,适用于非常弱的硬件,并且在这些测试中产生的结果相当糟糕。从那时起,已经创建了许多更好的随机数生成器,最好选择适合您需求的随机数生成器,而不是依赖rand()
.
哪个适合您的目的取决于您在做什么,例如您可能需要加密质量或多维生成,但对于许多用途,您只是希望事情是相当均匀随机的,快速生成,并且没有钱该行基于您可能需要xoroshiro128+生成器的结果质量。或者,您可以使用 C++<random>
标头中的一种方法,但提供的生成器不是最先进的,现在可以使用更好的方法,但是,它们对于大多数用途来说仍然足够好并且非常方便。
如果有钱(例如在线赌场中的洗牌等),或者您需要加密质量,您需要仔细调查适当的生成器并确保它们完全符合您的特定需求。
rand
由于历史原因,通常(但并非总是)是一个非常糟糕的伪随机数生成器(PRNG)。它有多糟糕是特定于实现的。
C++11 有很好的、更好的 PRNG。使用它的<random>
标准标题。请参阅std::uniform_int_distribution
此处,上面有一个很好的示例std::mersenne_twister_engine
。
PRNG 是一个非常棘手的主题。我对他们一无所知,但我相信专家。
让我添加另一个使 rand() 完全不可用的原因:该标准没有定义它生成的随机数的任何特征,既没有分布也没有范围。
如果没有定义分布,我们甚至无法将其包装成我们想要的分布。
更进一步,理论上我可以通过简单地返回 0 来实现 rand(),并宣布RAND_MAX
我的 rand() 是 0。
或者更糟糕的是,我可以让最低有效位始终为 0,这并不违反标准。想象有人写代码,如if (rand()%2) ...
.
实际上, rand() 是实现定义的,标准说:
无法保证产生的随机序列的质量,并且已知某些实现会产生具有令人痛苦的非随机低位比特的序列。具有特殊要求的应用程序应使用已知足以满足其需求的生成器
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf p36
如果您使用 rand(),则在生成随机数后,您将基本上得到相同的结果。因此,即使在使用 srand() 之后,如果有人能猜出您使用的种子,也很容易预测生成的数字。这是因为函数 rand() 使用特定的算法来产生这样的数字
有一些时间可以浪费,你可以弄清楚如何在给定种子的情况下预测函数生成的数字。你现在需要的只是猜测种子。有些人将种子称为当前时间。所以如果能猜出你运行应用程序的时间,我就能预测出这个数字
使用 RAND() 很糟糕!!!!