7

rand()尽管使用种子 via ,但通常不赞成使用 of srand()。为什么会这样?有什么更好的选择?

4

7 回答 7

17

这个故事有两个部分。

首先,rand是一个伪随机数生成器。这意味着它取决于种子。对于给定的种子,它将始终给出相同的序列(假设相同的实现)。这使得它不适合某些高度关注安全性的应用程序。但这并不特定于rand. 这是任何伪随机生成器的问题。并且肯定有很多类别的问题可以接受伪随机生成器。真正的随机生成器有其自身的问题(效率、实现、熵),因此对于与安全无关的问题,通常使用伪随机生成器。

因此,您分析了您的问题并得出结论,伪随机生成器是解决方案。在这里,我们遇到了 C 随机库(包括randsrand)的真正问题,这些问题特定于它并使其过时(又名:你永远不应该使用的原因rand和 C 随机库)。

  • 一个问题是它有一个全局状态(由 设置srand)。这使得不可能同时使用多个随机引擎。它还使多线程任务变得非常复杂。

  • 它最明显的问题是它缺少一个分发引擎rand给你一个间隔的数字[0 RAND_MAX]。它在这个区间内是均匀的,这意味着这个区间内的每个数字都有相同的概率出现。但大多数情况下,您需要一个特定间隔内的随机数。比方说[0, 1017]。一个常用(和幼稚)的公式是rand() % 1018. 但问题在于,除非RAND_MAX是精确的倍数,否则1018不会得到均匀分布。

  • 另一个问题是实施的质量rand。这里还有其他答案比我能更好地详细说明这一点,所以请阅读它们。

在现代 C++ 中,您绝对应该使用 C++ 库,<random>其中包含多个随机定义明确的引擎以及整数和浮点类型的各种分布。

于 2018-10-18T08:29:34.090 回答
6

rand() 这里的答案都没有解释坏的真正原因。

rand()是一个伪随机数生成器 (PRNG),但这并不意味着它一定是坏的。实际上,有非常好的 PRNG,它们在统计上很难或不可能与真正的随机数区分开来。

rand()是完全实现定义的,但从历史上看,它是作为线性同余生成器 (LCG)实现的,这通常是一种快速但臭名昭著的 PRNG 类。这些生成器的低位具有比高位低得多的统计随机性,并且生成的数字可以产生可见的晶格和/或平面结构(最好的例子是著名的RANDU PRNG)。一些实现尝试通过将位右移一个预定义的量来减少低位问题,但是这种解决方案也减少了输出的范围。

尽管如此,还是有一些优秀的 LCG 的显着例子,例如 L'Ecuyer 的 64 位和 128 位乘法线性同余发生器,在不同大小和良好晶格结构的线性同余发生器表中提出,Pierre L'Ecuyer,1999 年

一般的经验法则是不要信任rand(),使用您自己的符合您的需求和使用要求的伪随机数生成器。

于 2018-10-18T08:49:15.737 回答
5

rand/的坏处srandrand——</p>

  • 对其生成的数字序列使用未指定的算法,但
  • 允许将该算法初始化srand为可重复的“随机性”。

这两点结合在一起,阻碍了实现改进实现的能力rand(例如,使用加密随机数生成器 [RNG] 或其他“更好”的算法来生成伪随机数)。例如,JavaScriptMath.random和 FreeBSDarc4random没有这个问题,因为它们不允许应用程序为可重复的“随机性”播种它们——正是由于这个原因,V8 JavaScript 引擎能够将其Math.random实现更改为xorshift128+while的变体保持向后兼容性。(另一方面,让应用程序提供额外的数据来补充“随机性”,如 中的BCryptGenRandom,问题较小;但即便如此,

还:

  • 和 的算法和播种过程未指定这一事实rand意味着srand即使在rand/srand实现之间、同一标准库的版本之间、操作系统之间等之间也不能保证可重现的“随机性”。
  • 如果srand在 is 之前未调用,randrand其行为类似于srand(1)第一次调用。在实践中,这意味着它rand只能实现为伪随机数生成器 (PRNG) 而不是非确定性 RNG,并且rand无论应用程序是否调用,PRNG 算法在给定实现中都不会有所不同srand

编辑(2020 年 7 月 8 日):

还有一件更重要的事情是不好的randsrand。这些函数的 C 标准中没有任何内容指定由所传递的“伪随机数”rand必须遵循的特定分布,包括均匀分布,甚至是近似于均匀分布的分布。将此与 C++uniform_int_distributionuniform_real_distribution类以及 C++ 指定的特定伪随机生成器算法(例如linear_congruential_enginemt19937.

编辑(2020 年 12 月 12 日开始):

rand关于and的另一个坏处是srandsrand种子只能像unsigned. unsigned必须至少为 16 位,并且在大多数主流 C 实现中,unsigned16 位或 32 位,具体取决于实现的数据模型(尤其不是 64 位,即使 C 实现采用 64 位数据模型)。因此,通过这种方式可以选择不超过 2^N 个不同的数字序列(其中 N 是 an 中的位数unsigned),即使实现的底层算法rand可以产生比这更多的不同序列(例如,2^128甚至是 C++ 中的 2^19937 mt19937)。

于 2018-10-18T19:48:35.300 回答
2

首先,srand()没有得到种子,它设置了种子。播种是使用任何伪随机数生成器 (PRNG) 的一部分。当播种时,PRNG 从该种子产生的数字序列是严格确定的,因为(大多数?)计算机无法生成真正的随机数。更改您的 PRNG 不会阻止序列从种子中可重复,事实上,这是一件好事,因为产生相同的伪随机数序列的能力通常很有用。

那么,如果所有 PRNG 都共享这个特性,rand()为什么会被rand()认为是坏的呢?好吧,它归结为伪随机的“伪”部分。我们知道 PRNG 不可能是真正随机的,但我们希望它的行为尽可能接近真正的随机数生成器,并且可以应用各种测试来检查 PRNG 序列与真正的随机序列的相似程度. 尽管标准未指定其实现,但rand()在每个常用的编译器中,都使用了一种非常古老的生成方法,适用于非常弱的硬件,并且在这些测试中产生的结果相当糟糕。从那时起,已经创建了许多更好的随机数生成器,最好选择适合您需求的随机数生成器,而不是依赖rand().

哪个适合您的目的取决于您在做什么,例如您可能需要加密质量或多维生成,但对于许多用途,您只是希望事情是相当均匀随机的,快速生成,并且没有钱该行基于您可能需要xoroshiro128+生成器的结果质量。或者,您可以使用 C++<random>标头中的一种方法,但提供的生成器不是最先进的,现在可以使用更好的方法,但是,它们对于大多数用途来说仍然足够好并且非常方便。

如果有钱(例如在线赌场中的洗牌等),或者您需要加密质量,您需要仔细调查适当的生成器并确保它们完全符合您的特定需求。

于 2018-10-18T08:32:44.413 回答
1

rand由于历史原因,通常(但并非总是)是一个非常糟糕的伪随机数生成器(PRNG)。它有多糟糕是特定于实现的。

C++11 有很好的、更好的 PRNG。使用它的<random>标准标题。请参阅std::uniform_int_distribution 此处,上面有一个很好的示例std::mersenne_twister_engine

PRNG 是一个非常棘手的主题。我对他们一无所知,但我相信专家。

于 2018-10-18T08:16:45.957 回答
0

让我添加另一个使 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

于 2021-12-02T09:31:51.727 回答
-4

如果您使用 rand(),则在生成随机数后,您将基本上得到相同的结果。因此,即使在使用 srand() 之后,如果有人能猜出您使用的种子,也很容易预测生成的数字。这是因为函数 rand() 使用特定的算法来产生这样的数字

有一些时间可以浪费,你可以弄清楚如何在给定种子的情况下预测函数生成的数字。你现在需要的只是猜测种子。有些人将种子称为当前时间。所以如果能猜出你运行应用程序的时间,我就能预测出这个数字

使用 RAND() 很糟糕!!!!

于 2018-10-18T08:13:00.823 回答