测试应用程序最基本的问题是您先调用srand
一次,然后再调用rand
一次然后退出。
srand
函数的全部要点是初始化伪随机数的序列使用随机种子。
这意味着,如果传递相同的值,以srand
在两个不同的应用程序(使用相同srand
/ rand
执行),那么你会得到完全一样的序列中rand()
在这两个应用程序中读取值。
但是,在您的示例应用程序中,伪随机序列仅包含一个元素-从种子生成的伪随机序列的第一个元素等于当前时间 second
精确。您期望在输出中看到什么?
显然,当您碰巧在同一秒上运行应用程序时-您使用了相同的种子值-因此,结果当然是相同的(正如Martin York在对该问题的评论中已经提到的那样)。
实际上,您应该呼叫srand(seed)
一次,然后呼叫rand()
多次并分析该序列-它看起来应该是随机的。
编辑:
哦,我明白了。显然,口头描述是不够的(可能是语言障碍或其他... :))。
好。基于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;
}
^^^ THAT从节目单运行顺序应该看起来是随机的。
编辑2:
使用C或C ++标准库时,重要的是要了解,到目前为止,还没有单个标准函数或类可以确定地(实际上由标准保证)产生随机数据。解决此问题的唯一标准工具是std :: random_device,遗憾的是仍无法提供实际随机性的保证。
根据应用程序的性质,您首先应该确定您是否真的需要真正的随机(不可预测)数据。最明显的情况是您确实需要真正的随机性是信息安全性-例如,生成对称密钥,非对称私钥,盐值,安全性令牌等。
但是,安全级随机数是一个单独的行业,值得单独撰写。
在大多数情况下,伪随机数生成器就足够了-例如用于科学模拟或游戏。在某些情况下,甚至需要一致定义的伪随机序列-例如,在游戏中,您可以选择在运行时生成完全相同的地图,以避免存储大量数据。
最初的问题和重复出现的相同/相似问题(甚至是许多错误的“答案”)表明,首先重要的是要区分随机数和伪随机数,并了解什么是伪随机数序列。首先,要认识到伪随机数生成器的使用方式与使用真正的随机数生成器的方式不同。
直观地,当您请求随机数时-返回的结果不应该依赖于先前返回的值,也不应该依赖于是否有人在此之前请求了什么,也不应该依赖于哪个时刻,哪个进程,哪个计算机,来自哪个生成器以及位于哪个位置。要求什么星系。那就是“随机”这个词到底意味着什么-不可预测且独立于任何事物-否则它不再是随机的,对吗?凭着这种直觉,在网络上搜索一些魔术咒语以在任何可能的情况下获得这样的随机数是很自然的。
^^^ 在涉及伪随机数生成器的所有情况下,这种直觉期望都非常错误并且有害 -尽管对于真正的随机数而言是合理的。
尽管存在“随机数”的有意义的概念-但不存在“伪随机数”之类的东西。一个伪随机数生成器实际上产生伪随机数序列。
当专家谈论PRNG的质量时,他们实际上是在谈论所生成序列(及其引人注意的子序列)的统计特性。例如,如果您通过轮流使用两个高质量的PRNG来组合它们-您可能会产生不良的结果序列-尽管它们各自分别生成良好的序列(这两个良好的序列可能只是相互关联,从而导致组合不良)。
伪随机序列实际上始终是确定性的(由其算法和初始参数确定),即实际上没有随机性。
具体地说rand()
/ srand(s)
对函数提供了单数每进程非线程安全(!)与实现定义的算法产生的伪随机数序列。函数rand()
产生范围内的值[0, RAND_MAX]
。
引用C11标准:
该srand
函数将参数用作新的伪随机数序列的种子,该序列将由后续对的调用返回rand
。如果
srand
随后使用相同的种子值调用,则应重复伪随机数序列。如果rand
在进行任何调用之前调用了if srand
,则应生成与srand
首次调用when时相同的序列,其种子值为1。
许多人有理由期望这rand()
会产生一系列范围0
为到的半独立均匀分布的数RAND_MAX
。好吧,它绝对是应该的(否则它是无用的),但是不幸的是,不仅标准不要求这样做-甚至有明确的免责声明,指出“不能保证所产生的随机序列的质量”。在某些历史案例中rand
/ srand
实现的质量确实很差。即使在现代实现中,它很可能足够好-但是信任被破坏并且不容易恢复。除了它的非线程安全特性外,它在多线程应用程序中的安全使用也非常棘手和有限(仍然可能-您可以从一个专用线程使用它们)。
新的类模板std :: mersenne_twister_engine <>(及其方便的typedef-std::mt19937
/ std::mt19937_64
具有良好的模板参数组合)提供了每个对象了C ++ 11标准中定义的伪随机数生成器。使用相同的模板参数和相同的初始化参数,不同的对象将在任何使用C ++ 11兼容标准库构建的应用程序中的任何计算机上,生成完全相同的每个对象输出序列。此类的优点是其可预测的高质量输出序列和各个实现之间的完全一致性。
另外,在C ++ 11标准中定义了更多PRNG引擎-std :: linear_congruential_engine <>(srand/rand
在某些C标准库实现中历史上用作公平质量算法)和std :: subtract_with_carry_engine <>。它们还生成完全定义的参数相关的每个对象输出序列。
现代化的C ++ 11示例替换了上面的过时的C代码:
#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;
}