尽管使用 srand()
种子,但通常不建议使用 rand()
。为什么会这样呢?有哪些更好的替代方案可用?
这个故事分为两个部分。
首先,rand
是一个伪随机数生成器,这意味着它依赖于一个种子。对于给定的种子,它总是会产生相同的序列(假设实现相同)。这使得它不适合某些需要高度安全性的应用程序。 但是这并不是特定于rand
的问题。任何伪随机生成器都存在此问题。而且在许多情况下,使用伪随机生成器是可接受的,因为真正的随机生成器也存在其自身的问题(效率、实现、熵)。
所以你分析了问题,并得出结论:伪随机生成器是解决方案。然后我们来到了与 C 随机库(包括rand
和srand
)有关的真正麻烦的问题,这些问题是特定于它的,使其过时(也就是说,你永远不应该使用rand
和C随机库的原因)。
问题之一是它具有全局状态(由srand
设置)。这使得同时使用多个随机引擎变得不可能。这也大大复杂化了多线程任务。
IT技术最显著的问题是缺乏分发引擎: rand
给出一个在区间[0,RAND_MAX]
内的数字。它在这个区间内是均匀的,这意味着在这个区间内每个数字出现的概率相同。但通常你需要一个特定区间内的随机数。比如[0, 1017]
。常见(且天真)使用的公式是rand() % 1018
。但问题是,除非RAND_MAX
正好是1018
的倍数,否则你不会得到均匀分布。
另一个问题是rand
的实现质量。有其他答案更好地详细说明了这一点,请阅读它们。
在现代C++中,你应该绝对使用来自<random>
的C++库,其中包含多个随机定义良好的引擎和各种整数和浮点类型的分布。
rand()
函数。 - plasmacelrand
函数生成的“伪随机数”必须遵循特定的分布,包括均匀分布。 - Peter O.这里没有任何一个答案能够解释rand()
为什么会不好的真正原因。
rand()
是一个伪随机数生成器(PRNG),但这并不意味着它一定是差的。实际上,有非常好的PRNG,它们在统计学上很难或不可能与真正的随机数区分开来。
rand()
完全是实现定义的,但历史上它被实现为一个线性同余生成器(LCG),通常是一类快速但声名狼藉的PRNG。这些生成器的低位比高位具有更低的统计随机性,生成的数字可以产生可见的晶格和/或平面结构(最好的例子是著名的RANDU PRNG)。一些实现尝试通过将位向右移一个预定义的量来减少低位问题,然而这种解决方案也会减少输出的范围。
尽管如此,还有一些优秀的LCG值得注意,例如L'Ecuyer在Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure, Pierre L'Ecuyer, 1999 中介绍的64位和128位乘法线性同余生成器。
通常的经验法则是:不要相信rand()
,而是使用适合您需求和使用要求的自己的伪随机数生成器。
rand
/srand
有缺陷的地方在于rand
:
srand
初始化该算法以实现可重复的“随机性”。rand
的能力(例如,使用加密随机数生成器[RNG]或用于产生伪随机数的其他“更好”的算法)。例如,JavaScript的Math.random
和FreeBSD的arc4random
没有这个问题,因为它们不允许应用程序为可重复的“随机性”提供种子。正是由于这个原因,V8 JavaScript引擎能够将其Math.random
实现更改为xorshift128+
的变体,同时保持向后兼容性。(另一方面,让应用程序提供额外数据以补充“随机性”,如BCryptGenRandom
所示,问题就较少;即便如此,这通常只出现在加密RNG中。)rand
和srand
的规定未指定,这意味着即使在rand
/srand
实现之间(同一标准库的版本之间、操作系统之间等),可再现的“随机性”也不能保证。rand
之前没有调用srand
,那么rand
的行为类似于首先调用srand(1)
。实际上,这意味着rand
只能作为伪随机数生成器(PRNG)来实现,而不能作为非确定性RNG,并且无论应用程序是否调用srand
,rand
的PRNG算法在给定的实现中都不能有所不同。编辑(2020年7月8日):
rand
和srand
的一个更加重要的问题在于它们在C标准中没有规定所谓的“伪随机数”必须遵循特定的分布,包括均匀分布或者近似均匀分布。相比之下,C++的uniform_int_distribution
和uniform_real_distribution
类以及由C++指定的特定伪随机生成算法,如linear_congruential_engine
和mt19937
,都有明确的分布规定。请注意,本文编辑于2020年12月12日。
关于rand
和srand
,还有一个不好的地方:srand
需要一个种子,该种子只能达到无符号整数的大小。 unsigned
至少必须为16位,在大多数主流C实现中,unsigned
根据实现的数据模型,可能是16位或32位(即使C实现采用64位数据模型,也不会是64位)。因此,通过这种方式最多只能选择2^N个不同的数字序列(其中N是unsigned
中的位数),即使rand
实现的底层算法可以生成更多不同的序列(例如,像C ++的mt19937
一样可以产生2^128或甚至2^19937个序列)。
srand()
并不获取种子,而是设置种子。种子是任何伪随机数生成器(PRNG)使用的一部分。当给定种子后,PRNG从该种子产生的数字序列是严格确定性的,因为(大多数?)计算机没有生成真正随机数的手段。更改PRNG也不能阻止序列从种子开始重复出现,实际上这是有用的,因为能够产生相同的伪随机数序列通常很有用。rand()
共享此特性,为什么rand()
被认为不好呢?嗯,这要归结于“伪”这个词。我们知道PRNG不能真正地随机,但我们希望它的行为尽可能接近真正的随机数生成器,并且可以应用各种测试来检查PRNG序列与真正随机序列的相似程度。虽然标准未指定其实现方式,但在每个常用编译器中,rand()
使用一种非常老的生成方法,适用于非常弱的硬件,并且其结果在这些测试上表现不佳。自此以后,已经创建了许多更好的随机数生成器,最好选择适合您需求的一种,而不是依赖于rand()
提供的低质量随机数生成器。<random>
头文件中的方法,但所提供的生成器并非最先进,现在有更好的选择,但是它们对于大多数目的来说都足够好且非常方便。rand()
使用了一种非常古老的生成方法”--没有这样的要求。 - Pete Beckerrand
通常是一个非常糟糕的伪随机数生成器(PRNG),但并非总是如此,这是由其实现方式决定的。
C++11有很好的、更好的PRNG。使用其<random>
标准头文件。特别是看看这里的std::uniform_int_distribution
,其中有一个很好的示例{{link4:std::mersenne_twister_engine
}}。
PRNG是一个非常棘手的问题。我对它们一无所知,但我相信专家们。
让我再给你添加一个使rand()完全无法使用的理由:标准没有定义它生成的随机数的任何特性,包括分布和范围。
没有分布的定义,我们甚至无法将其封装为想要的分布。
更进一步地,理论上我可以通过简单地返回0来实现rand()函数,并宣称我的rand()的RAND_MAX
是0。
甚至更糟糕的是,我可以让最低有效位始终为0,这不违反标准。想象一下有人写了这样的代码:if (rand()%2) ...
。
实际上,rand()的实现是由编译器定义的,标准也如下所述:
不能保证生成的随机序列的质量,某些实现已知会产生具有非常低位非随机性的序列。对于需要特定要求的应用程序,应该使用已知能满足其需求的生成器。
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf p36
rand()
的一般问题。 - Jack Aidleyrand()
被实现得糟糕。 - Pete Becker
rand()
存在的一些问题。 - 463035818_is_not_a_numbertime(NULL)
每秒钟都会变化。如果您每秒运行它多次,您将得到相同的结果。 - VLL