【问题标题】:Why is the use of rand() considered bad?为什么使用 rand() 被认为是不好的?
【发布时间】:2019-03-22 23:25:54
【问题描述】:

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

【问题讨论】:

  • 您可以使用例如std::random_device,它可以用于许多数字分布。
  • 当我使用 srand(time(NULL)) 时种子会发生变化,但仍不建议使用它。为什么会这样?
  • 这个video 有点夸大了问题,但它很好地解释了rand() 的一些问题
  • @Sid time(NULL) 每秒都在变化。如果你每秒运行多次,你会得到相同的结果。

标签: c++ random lcg


【解决方案1】:

这个故事有两个部分。

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

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

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

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

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

在现代 C++ 中,您绝对应该使用来自 <random> 的 C++ 库,它带有多个随机定义良好的引擎以及整数和浮点类型的各种分布。

【讨论】:

  • 太棒了!这就是我一直在寻找的答案!
  • 所有 PRNG 都没有“分发引擎”。分布从 PRNG 中获取原始随机值并对其值进行采样/转换以适应分布。如果您编写一个模仿 C++ PRNG 类型接口的包装函子类,则可以使用任何带有 rand() 的 C++ 随机分布。
  • @plasmacel 非常正确。我主要考虑的是 C 随机库作为一个整体与 C++11 随机库作为一个整体,但这并没有进入书面文字:)。我改写了帖子。谢谢,非常好的观点。
  • C 的标准甚至没有指定rand 提供的“伪随机数”必须遵循的特定分布,包括均匀分布。
【解决方案2】:

这里的答案都没有解释成为rand()的真正原因糟糕

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

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

仍然有一些出色的 LCG 的显着例子,例如 L'Ecuyer 的 64 位和 128 位乘法线性同余发生器,在 不同大小和良好晶格结构的线性同余发生器表中介绍,Pierre L'Ecuyer,1999。

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

【讨论】:

    【解决方案3】:

    rand/srand 的坏处在于rand——

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

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

    还有:

    • randsrand 的算法和播种过程未指定这一事实意味着即使在rand/srand 实现、between versions of the same standard library、操作系统之间也不能保证可重现的“随机性”,等
    • 如果在rand 之前没有调用srand,则rand 的行为类似于第一次调用srand(1)。在实践中,这意味着rand 只能实现为伪随机数生成器 (PRNG) 而不是非确定性 RNG,并且rand 的 PRNG 算法在给定的实现中不会有所不同,无论应用程序调用 @ 987654346@与否。

    编辑(2020 年 7 月 8 日):

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

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

    关于randsrand 的另一个坏处是:srand 的种子只能和unsigned 一样大。 unsigned 必须至少为 16 位,在大多数主流 C 实现中,unsignedeither 16 or 32 bits depending on the implementation's data model(特别是不是 64 位,即使 C 实现采用 64 位数据模型)。因此,通过这种方式最多可以选择 2^N 个不同的数字序列(其中 N 是 unsigned 中的位数),即使 rand 实现的底层算法可以产生比这更多的不同序列(比如 C++ 的mt19937 中的 2^128 甚至 2^19937)。

    【讨论】:

    • 今天的 C 实现仍然是 32 位的吗?
    • @heretoinfinity:就本答案而言,srand 将单个 unsigned 作为其种子,size of unsigned 必须至少为 16 位,但通常为 16 或 32 (尤其是在采用 64 位数据模型的 C 实现中也不是 64)。
    • 哇。最后一点是惊喜。感谢您的更新。
    【解决方案4】:

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

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

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

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

    【讨论】:

    • 我的意思是使用 srand 获得种子,而不是它获得种子。对不起,如果我不清楚...
    • Re: "rand() 使用了一种非常古老的生成方法" -- 没有这样的要求。
    • @PeteBecker:没有这样的要求,没有,但是所有常用的 C++ 编译器使用这样的方法。该标准是谈论 C、C++ 或任何其他语言实际行为方式的愚蠢方式。
    • @JackAidley - 相反:笼统的概括(特别是,“所有常用的 C++ 编译器......)是谈论 C、C++ 或任何其他语言如何行为的愚蠢方式。如果您想做出准确的陈述,请输入适当的限定符。如果您说“我检查过其库的所有编译器(到目前为止并非所有现有编译器)都......”那将是不同的问题(当然,假设您实际上已经进行了此类调查或可以以其他方式验证此类声明)。
    • @Jack Aidley:你说的播种的意思是一样的。
    【解决方案5】:

    rand 通常 - 但并非总是 - 由于历史原因,是一个非常糟糕的 pseudo-random number generator (PRNG)。具体实现有多糟糕。

    C++11 有很好的、更好的 PRNG。使用它的<random> standard header。尤其是std::uniform_int_distributionhere,上面有一个很好的例子std::mersenne_twister_engine

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

    【讨论】:

      【解决方案6】:

      让我添加另一个使 rand() 完全不可用的原因:该标准没有定义它生成的随机数的任何特征,既没有分布也没有范围。

      如果没有定义分布,我们甚至无法将其包装成我们想要的分布。

      更进一步,理论上我可以通过简单地返回 0 来实现 rand(),并宣布我的 rand() 的 RAND_MAX 为 0。

      或者更糟糕的是,我可以让最低有效位始终为 0,这并不违反标准。想象有人写了if (rand()%2) ...之类的代码。

      实际上,rand() 是实现定义的,标准说:

      不保证产生的随机序列的质量和一些实现 已知会产生令人痛苦的非随机低位序列。应用程序 特定要求应使用已知足以满足其需求的生成器

      http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdfp36

      【讨论】:

        【解决方案7】:

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

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

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

        【讨论】:

        • 算法是实现定义的。见stackoverflow.com/questions/1026327/…
        • 您指定的问题只有在您对生成器有特定需求时才会出现问题。 rand() 不是一般问题。
        • 嗯,每个 伪随机数生成器使用特定的算法来产生结果。能否预测下一个数字取决于算法的细节。 C 和 C++ 都不要求 rand() 被糟糕地实现。
        猜你喜欢
        • 2021-11-26
        • 2010-11-04
        • 2020-05-19
        • 2015-02-12
        • 2010-10-22
        相关资源
        最近更新 更多