您的测试应用程序最根本的问题是您调用srand
一次,然后调用rand
一次并退出。
函数的重点是用随机种子srand
初始化伪随机数序列。
这意味着如果您将相同的值传递给srand
两个不同的应用程序(具有相同的srand
/rand
实现),那么您将在两个应用程序中获得完全相同的值rand()
序列。
但是,在您的示例应用程序中,伪随机序列仅包含一个元素 - 从种子生成的伪随机序列的第一个元素等于当前1 sec
精度时间。你期望在输出上看到什么?
显然,当您碰巧在同一秒运行应用程序时-您使用相同的种子值-因此您的结果当然是相同的(正如 Martin York 在对该问题的评论中已经提到的那样)。
实际上,您应该调用srand(seed)
一次,然后调用rand()
多次并分析该序列-它应该看起来是随机的。
修正 1 - 示例代码:
好的我明白了。显然口头描述是不够的(可能是语言障碍之类的...... :))。
srand()/rand()/time()
基于问题中使用的相同函数的老式 C 代码示例:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^单次运行的程序应该看起来是随机的。
请注意,出于下面解释的原因,我不建议在生产中使用rand
/srand
函数,并且我绝对不建议将函数time
用作随机种子,因为 IMO 已经很明显了。这些对于教育目的很好,有时也可以说明这一点,但对于任何严肃的用途,它们大多是无用的。
修正案 2 - 详细说明:
重要的是要理解,到目前为止,还没有C 或 C++ 标准特性(库函数或类)最终确定地产生实际随机数据(即由标准保证实际上是随机的)。解决这个问题的唯一标准特性是std::random_device,遗憾的是它仍然不能保证实际随机性。
根据应用程序的性质,您应该首先确定您是否真的需要真正随机(不可预测的)数据。当你确实需要真正的随机性时,值得注意的情况是信息安全——例如生成对称密钥、非对称私钥、盐值、安全令牌等。
然而,安全级随机数是一个独立的行业,值得单独写一篇文章。我在我的这个答案中简要讨论了它们。
在大多数情况下,伪随机数生成器就足够了——例如用于科学模拟或游戏。在某些情况下,甚至需要一致定义的伪随机序列——例如在游戏中,您可以选择在运行时生成完全相同的地图,以避免在您的分布中存储大量数据。
最初的问题和反复出现的大量相同/相似的问题(甚至许多被误导的“答案”)表明,首先重要的是区分随机数和伪随机数,并了解什么是伪随机数序列首先并要意识到伪随机数生成器的使用方式与使用真随机数生成器的方式不同。
直观地,当您请求随机数时 - 返回的结果不应依赖于先前返回的值,不应依赖于之前是否有人请求过任何东西,也不应依赖于什么时候、通过什么过程、在什么计算机上、来自什么生成器和在它被要求的星系。毕竟,这就是“随机”这个词的含义——不可预测且独立于任何事物——否则它不再是随机的,对吧?有了这种直觉,在网络上搜索一些魔法咒语以在任何可能的情况下获得这样的随机数是很自然的。
^^^这种直观的期望在涉及伪随机数生成器的所有情况下都是非常错误和有害的——尽管对于真正的随机数是合理的。
虽然存在“随机数”的有意义的概念(有点) - 没有“伪随机数”这样的东西。伪随机数生成器实际上产生伪随机数序列。
伪随机序列实际上总是确定性的(由它的算法和初始参数决定)——也就是说,它实际上没有任何随机性。
当专家谈论 PRNG 的质量时,他们实际上谈论的是生成序列(及其显着的子序列)的统计特性。例如,如果您通过轮流使用它们来组合两个高质量的 PRNG - 您可能会产生不好的结果序列 - 尽管它们分别生成好的序列(这两个好的序列可能只是相互关联,因此组合不好)。
具体来说rand()
/srand(s)
一对函数提供了一个单一的每进程非线程安全(!)伪随机数序列,使用实现定义的算法生成。函数rand()
产生范围内的值[0, RAND_MAX]
。
引用 C11 标准 (ISO/IEC 9899:2011):
该srand
函数使用该参数作为一个新的伪随机数序列的种子,这些伪随机数将由后续调用返回rand
。如果
srand
然后以相同的种子值调用,则应重复伪随机数序列。如果rand
在进行任何调用之前调用srand
,则应生成与srand
第一次调用时相同的序列,种子值为 1。
许多人合理地期望这rand()
将产生一系列半独立均匀分布的数字,范围0
为RAND_MAX
。好吧,它当然应该(否则它没用),但不幸的是,不仅标准不需要这样做 - 甚至还有明确的免责声明指出“无法保证产生的随机序列的质量”。在某些历史案例rand
/srand
实施中确实质量很差。即使在现代实现中它很可能已经足够好 - 但信任被打破并且不容易恢复。除了它的非线程安全特性之外,它在多线程应用程序中的安全使用也变得棘手和有限(仍然可能——您可以只从一个专用线程中使用它们)。
新的类模板std::mersenne_twister_engine<>(及其便利的 typedefs - std::mt19937
/std::mt19937_64
具有良好的模板参数组合)提供了在 C++11 标准中定义的每个对象的伪随机数生成器。使用相同的模板参数和相同的初始化参数,不同的对象将在使用符合 C++11 标准库的任何应用程序中的任何计算机上生成完全相同的每个对象输出序列。此类的优势在于其可预测的高质量输出序列和跨实现的完全一致性。
在 C++11 标准中还定义了更多 PRNG 引擎 - std::linear_congruential_engine<>(历史上在某些 C 标准库实现中用作公平质量srand/rand
算法)和std::subtract_with_carry_engine<>。它们还生成完全定义的参数相关的每个对象输出序列。
上面过时的 C 代码的现代 C++11 示例替换:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
使用std::uniform_int_distribution<>的先前代码的版本
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}