为什么使用 rand() 被认为是不好的?
Why is the use of rand() considered bad?
尽管通过 srand()
使用种子,但通常不赞成使用 rand()
。为什么会这样呢?有什么更好的选择?
如果使用rand(),生成随机数后基本上会得到相同的结果。
因此,即使在使用 srand() 之后,如果有人能猜出您使用的种子,也很容易预测生成的数字。这是因为函数 rand() 使用了特定的算法来产生这样的数字
有一些时间可以浪费,您可以弄清楚如何在给定种子的情况下预测函数生成的数字。您现在所需要做的就是猜测种子。有些人将种子称为当前时间。所以如果能猜到你运行申请的时间,我就能预测出人数
不适合使用 RAND()!!!!
由于历史原因,rand
通常 - 但并非总是 - 非常糟糕的 pseudo-random number generator (PRNG)。具体实施有多糟糕。
C++11 有更好的 PRNG。使用其 <random>
standard header. See notably std::uniform_int_distribution
here which has a nice example above std::mersenne_twister_engine
.
PRNG 是一个非常棘手的主题。我对他们一无所知,但我相信专家。
这个故事分为两部分。
首先,rand
是一个pseudorandom number generator。这意味着它取决于种子。对于给定的种子,它总是给出相同的序列(假设相同的实现)。这使得它不适用于某些非常关注安全性的应用程序。 但是 这不是 rand
特有的。这是任何伪随机生成器的问题。并且肯定有很多 类 的问题可以接受伪随机生成器。真正的随机生成器有其自身的问题(效率、实现、熵),因此对于与安全无关的问题,通常使用伪随机生成器。
所以您分析了您的问题并得出结论,伪随机生成器是解决方案。在这里,我们遇到了特定于它的 C 随机库(包括 rand
和 srand
)的真正麻烦,并使其 过时(a.k.a.: 你应该 永远不会 使用 rand
和 C 随机库的原因。
一个问题是它具有 全局状态(由 srand
设置)。这使得无法同时使用多个随机引擎。它还使多线程任务变得非常复杂。
它最明显的问题是它缺少分发引擎:rand
给你一个区间[0 RAND_MAX]
.在这个区间内是均匀的,也就是说这个区间内的每个数字出现的概率都是一样的。但大多数情况下,您需要一个特定时间间隔内的随机数。假设 [0, 1017]
。一个常用(和天真的)使用的公式是 rand() % 1018
。但问题在于,除非 RAND_MAX
是 1018
的精确倍数,否则您不会得到均匀分布。
另一个问题是 rand
的实施质量。这里还有其他答案比我更详细地说明了这一点,所以请阅读它们。
在现代 C++ 中,您绝对应该使用来自 <random>
的 C++ 库,它带有多个随机定义良好的引擎以及整数和浮点类型的各种分布。
首先,srand()
没有获得种子,它设置了种子。播种是使用任何伪随机数生成器 (PRNG) 的一部分。当播种时,PRNG 从该种子产生的数字序列是严格确定的,因为(大多数?)计算机无法生成真正的随机数。更改您的 PRNG 不会阻止序列从种子中重复,事实上,这是一件好事,因为生成相同的 pseudo-random 数字序列的能力通常很有用。
所以如果所有 PRNG 都与 rand()
共享此功能,为什么 rand()
被认为是坏的?好吧,它归结为 pseudo-random 的 "psuedo" 部分。我们知道 PRNG 不可能是真正随机的,但我们希望它的行为尽可能接近真正的随机数生成器,并且有 various tests 可用于检查 PRNG 序列与真正的随机序列。尽管标准未指定其实现,但 rand()
在每个常用的编译器中都使用一种非常古老的生成方法,适用于非常弱的硬件,并且它在这些测试中产生的结果相当糟糕。从那时起,已经创建了许多更好的随机数生成器,最好选择一个适合您需要的随机数生成器,而不是依赖 rand()
.
可能提供的质量较差的生成器。
哪个适合您的目的取决于您在做什么,例如您可能需要加密质量,或 multi-dimensional 生成,但对于许多用途,您只是希望事物相当均匀地随机、快速一代,并且根据您可能想要 xoroshiro128+ generator. Alternatively you could use one of the methods in C++'s <random>
header 结果的质量,金钱并不重要,但是提供的发电机不是最先进的,现在可以使用更好的发电机,但是,它们对于大多数用途来说仍然足够好,而且非常方便。
如果钱悬而未决(例如在线赌场洗牌等),或者您需要加密质量,您需要仔细调查合适的生成器并确保它们完全满足您的特定需求。
None 的答案解释了 rand()
不好 .
的真正原因
rand()
是一个pseudo-random number generator (PRNG),但这并不意味着它一定是坏的。实际上,有非常好的 PRNG,从统计上很难或不可能与真正的随机数区分开来。
rand()
完全由实现定义,但历史上它是作为 Linear Congruential Generator (LCG), which is usually a fast, but notoriously bad class of PRNGs. The lower bits of these generators have much lower statistical randomness than the higher bits and the generated numbers can produce visible lattice and/or planar structures (the best example of that is the famous RANDU PRNG 实现的)。一些实现试图通过将位右移一个预定义的量来减少低位问题,但是这种解决方案也会减少输出的范围。
仍然有一些出色的 LCG 的显着示例,例如 Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure,Pierre L' 中介绍的 L'Ecuyer 的 64 位和 128 位乘法线性同余生成器埃库耶, 1999.
一般的经验法则是不要相信 rand()
,请使用适合您的需求和使用要求的您自己的伪随机数生成器。
rand
/srand
不好的地方在于rand
—
- 对它生成的数字序列使用未指定的算法,但是
- 允许使用
srand
初始化该算法以获得可重复的“随机性”。
这两点加在一起,阻碍了实现改进 rand
实现的能力(例如,使用加密随机数生成器 [RNG] 或其他“更好”的算法来生成伪随机数)。例如,JavaScript 的 Math.random
和 FreeBSD 的 arc4random
没有这个问题,因为它们不允许应用程序为可重复的“随机性”播种它们——正是出于这个原因V8 JavaScript 引擎能够将其 Math.random
实现更改为 xorshift128+
的变体,同时保持向后兼容性。 (另一方面,让应用程序向 补充 “随机性”提供额外的数据,如 BCryptGenRandom
中那样,问题较小;尽管如此,然而,这通常仅在加密 RNG。)
还有:
rand
和 srand
的算法和播种程序未指定这一事实意味着 rand
/srand
之间甚至无法保证可重现的“随机性”实现,between versions of the same standard library,操作系统之间等
- 如果在
rand
之前未调用 srand
,则 rand
的行为与首次调用 srand(1)
类似。在实践中,这意味着 rand
只能作为伪随机数生成器 (PRNG) 而不是非确定性 RNG 来实现,并且 rand
的 PRNG 算法在给定的实现中不会有所不同应用程序是否调用 srand
。
编辑(2020 年 7 月 8 日):
rand
和 srand
还有一个更重要的缺点。这些函数的 C 标准中没有指定 rand
提供的“伪随机数”必须遵循的特定分布,包括均匀分布甚至 近似 [=74= 的分布] 均匀分布。对比一下C++的uniform_int_distribution
和uniform_real_distribution
类,以及C++规定的具体伪随机生成算法,比如linear_congruential_engine
和mt19937
.
编辑(2020 年 12 月 12 日开始):
关于 rand
和 srand
的另一个坏处:srand
takes a seed that can only be as big as an unsigned
. unsigned
must be at least 16 bits and in most mainstream C implementations, unsigned
is either 16 or 32 bits depending on the implementation's data model(特别是不是 64 位,即使 C 实现采用 64 位数据模型)。因此,即使 rand
实现的基础算法可以产生更多的数字,也不能以这种方式选择超过 2^N 个不同的数字序列(其中 N 是 unsigned
中的位数)与此不同的序列(比如 C++ 中的 2^128 甚至 2^19937 mt19937
)。
让我补充另一个导致 rand() 完全不可用的原因:该标准没有定义它生成的随机数的任何特征,既没有分布也没有范围。
如果没有定义分布,我们甚至无法将其包装成我们想要的分布。
更进一步,理论上我可以通过简单地 return 0 来实现 rand(),并宣布我的 rand() 的 RAND_MAX
是 0。
或者更糟的是,我可以让最低有效位始终为0,这并不违反标准。想象有人写了像 if (rand()%2) ...
.
这样的代码
实际上,rand() 是实现定义的,标准说:
There are no guarantees as to the quality of the random sequence produced and some implementations
are known to produce sequences with distressingly non-random low-order bits. Applications with
particular requirements should use a generator that is known to be sufficient for their needs
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf p36
尽管通过 srand()
使用种子,但通常不赞成使用 rand()
。为什么会这样呢?有什么更好的选择?
如果使用rand(),生成随机数后基本上会得到相同的结果。 因此,即使在使用 srand() 之后,如果有人能猜出您使用的种子,也很容易预测生成的数字。这是因为函数 rand() 使用了特定的算法来产生这样的数字
有一些时间可以浪费,您可以弄清楚如何在给定种子的情况下预测函数生成的数字。您现在所需要做的就是猜测种子。有些人将种子称为当前时间。所以如果能猜到你运行申请的时间,我就能预测出人数
不适合使用 RAND()!!!!
rand
通常 - 但并非总是 - 非常糟糕的 pseudo-random number generator (PRNG)。具体实施有多糟糕。
C++11 有更好的 PRNG。使用其 <random>
standard header. See notably std::uniform_int_distribution
here which has a nice example above std::mersenne_twister_engine
.
PRNG 是一个非常棘手的主题。我对他们一无所知,但我相信专家。
这个故事分为两部分。
首先,rand
是一个pseudorandom number generator。这意味着它取决于种子。对于给定的种子,它总是给出相同的序列(假设相同的实现)。这使得它不适用于某些非常关注安全性的应用程序。 但是 这不是 rand
特有的。这是任何伪随机生成器的问题。并且肯定有很多 类 的问题可以接受伪随机生成器。真正的随机生成器有其自身的问题(效率、实现、熵),因此对于与安全无关的问题,通常使用伪随机生成器。
所以您分析了您的问题并得出结论,伪随机生成器是解决方案。在这里,我们遇到了特定于它的 C 随机库(包括 rand
和 srand
)的真正麻烦,并使其 过时(a.k.a.: 你应该 永远不会 使用 rand
和 C 随机库的原因。
一个问题是它具有 全局状态(由
srand
设置)。这使得无法同时使用多个随机引擎。它还使多线程任务变得非常复杂。它最明显的问题是它缺少分发引擎:
rand
给你一个区间[0 RAND_MAX]
.在这个区间内是均匀的,也就是说这个区间内的每个数字出现的概率都是一样的。但大多数情况下,您需要一个特定时间间隔内的随机数。假设[0, 1017]
。一个常用(和天真的)使用的公式是rand() % 1018
。但问题在于,除非RAND_MAX
是1018
的精确倍数,否则您不会得到均匀分布。另一个问题是
rand
的实施质量。这里还有其他答案比我更详细地说明了这一点,所以请阅读它们。
在现代 C++ 中,您绝对应该使用来自 <random>
的 C++ 库,它带有多个随机定义良好的引擎以及整数和浮点类型的各种分布。
首先,srand()
没有获得种子,它设置了种子。播种是使用任何伪随机数生成器 (PRNG) 的一部分。当播种时,PRNG 从该种子产生的数字序列是严格确定的,因为(大多数?)计算机无法生成真正的随机数。更改您的 PRNG 不会阻止序列从种子中重复,事实上,这是一件好事,因为生成相同的 pseudo-random 数字序列的能力通常很有用。
所以如果所有 PRNG 都与 rand()
共享此功能,为什么 rand()
被认为是坏的?好吧,它归结为 pseudo-random 的 "psuedo" 部分。我们知道 PRNG 不可能是真正随机的,但我们希望它的行为尽可能接近真正的随机数生成器,并且有 various tests 可用于检查 PRNG 序列与真正的随机序列。尽管标准未指定其实现,但 rand()
在每个常用的编译器中都使用一种非常古老的生成方法,适用于非常弱的硬件,并且它在这些测试中产生的结果相当糟糕。从那时起,已经创建了许多更好的随机数生成器,最好选择一个适合您需要的随机数生成器,而不是依赖 rand()
.
哪个适合您的目的取决于您在做什么,例如您可能需要加密质量,或 multi-dimensional 生成,但对于许多用途,您只是希望事物相当均匀地随机、快速一代,并且根据您可能想要 xoroshiro128+ generator. Alternatively you could use one of the methods in C++'s <random>
header 结果的质量,金钱并不重要,但是提供的发电机不是最先进的,现在可以使用更好的发电机,但是,它们对于大多数用途来说仍然足够好,而且非常方便。
如果钱悬而未决(例如在线赌场洗牌等),或者您需要加密质量,您需要仔细调查合适的生成器并确保它们完全满足您的特定需求。
None 的答案解释了 rand()
不好 .
rand()
是一个pseudo-random number generator (PRNG),但这并不意味着它一定是坏的。实际上,有非常好的 PRNG,从统计上很难或不可能与真正的随机数区分开来。
rand()
完全由实现定义,但历史上它是作为 Linear Congruential Generator (LCG), which is usually a fast, but notoriously bad class of PRNGs. The lower bits of these generators have much lower statistical randomness than the higher bits and the generated numbers can produce visible lattice and/or planar structures (the best example of that is the famous RANDU PRNG 实现的)。一些实现试图通过将位右移一个预定义的量来减少低位问题,但是这种解决方案也会减少输出的范围。
仍然有一些出色的 LCG 的显着示例,例如 Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure,Pierre L' 中介绍的 L'Ecuyer 的 64 位和 128 位乘法线性同余生成器埃库耶, 1999.
一般的经验法则是不要相信 rand()
,请使用适合您的需求和使用要求的您自己的伪随机数生成器。
rand
/srand
不好的地方在于rand
—
- 对它生成的数字序列使用未指定的算法,但是
- 允许使用
srand
初始化该算法以获得可重复的“随机性”。
这两点加在一起,阻碍了实现改进 rand
实现的能力(例如,使用加密随机数生成器 [RNG] 或其他“更好”的算法来生成伪随机数)。例如,JavaScript 的 Math.random
和 FreeBSD 的 arc4random
没有这个问题,因为它们不允许应用程序为可重复的“随机性”播种它们——正是出于这个原因V8 JavaScript 引擎能够将其 Math.random
实现更改为 xorshift128+
的变体,同时保持向后兼容性。 (另一方面,让应用程序向 补充 “随机性”提供额外的数据,如 BCryptGenRandom
中那样,问题较小;尽管如此,然而,这通常仅在加密 RNG。)
还有:
rand
和srand
的算法和播种程序未指定这一事实意味着rand
/srand
之间甚至无法保证可重现的“随机性”实现,between versions of the same standard library,操作系统之间等- 如果在
rand
之前未调用srand
,则rand
的行为与首次调用srand(1)
类似。在实践中,这意味着rand
只能作为伪随机数生成器 (PRNG) 而不是非确定性 RNG 来实现,并且rand
的 PRNG 算法在给定的实现中不会有所不同应用程序是否调用srand
。
编辑(2020 年 7 月 8 日):
rand
和 srand
还有一个更重要的缺点。这些函数的 C 标准中没有指定 rand
提供的“伪随机数”必须遵循的特定分布,包括均匀分布甚至 近似 [=74= 的分布] 均匀分布。对比一下C++的uniform_int_distribution
和uniform_real_distribution
类,以及C++规定的具体伪随机生成算法,比如linear_congruential_engine
和mt19937
.
编辑(2020 年 12 月 12 日开始):
关于 rand
和 srand
的另一个坏处:srand
takes a seed that can only be as big as an unsigned
. unsigned
must be at least 16 bits and in most mainstream C implementations, unsigned
is either 16 or 32 bits depending on the implementation's data model(特别是不是 64 位,即使 C 实现采用 64 位数据模型)。因此,即使 rand
实现的基础算法可以产生更多的数字,也不能以这种方式选择超过 2^N 个不同的数字序列(其中 N 是 unsigned
中的位数)与此不同的序列(比如 C++ 中的 2^128 甚至 2^19937 mt19937
)。
让我补充另一个导致 rand() 完全不可用的原因:该标准没有定义它生成的随机数的任何特征,既没有分布也没有范围。
如果没有定义分布,我们甚至无法将其包装成我们想要的分布。
更进一步,理论上我可以通过简单地 return 0 来实现 rand(),并宣布我的 rand() 的 RAND_MAX
是 0。
或者更糟的是,我可以让最低有效位始终为0,这并不违反标准。想象有人写了像 if (rand()%2) ...
.
实际上,rand() 是实现定义的,标准说:
There are no guarantees as to the quality of the random sequence produced and some implementations are known to produce sequences with distressingly non-random low-order bits. Applications with particular requirements should use a generator that is known to be sufficient for their needs
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf p36