你的测试应用最根本的问题是你调用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,遗憾的是它仍然不能保证实际随机性。
根据应用程序的性质,您应该首先确定您是否真的需要真正随机(不可预测)的数据。值得注意的情况当你确实需要真正的随机性时是信息安全 - 例如生成对称密钥、非对称私钥、盐值、安全令牌等。
但是,安全级随机数是一个独立的行业,值得单独写一篇文章。我正在我的this answer 中简要讨论它们。
在大多数情况下,Pseudo-Random Number Generator 就足够了 - 例如。用于科学模拟或游戏。在某些情况下,甚至需要一致定义的伪随机序列 - 例如在游戏中,您可以选择在运行时生成完全相同的地图,以避免在您的分布中存储大量数据。
最初的问题和重复出现的大量相同/相似的问题(甚至是许多被误导的“答案”)表明,首先重要的是区分随机数和伪随机数并了解什么是伪随机数首先是数字序列,并且要意识到伪随机数生成器的使用方式与使用真随机数生成器的方式不同。
直观地当您请求随机数时 - 返回的结果不应依赖于先前返回的值,也不应依赖于
任何人之前都要求过任何东西,不应该取决于什么时候
以及通过什么过程,在什么计算机上,从什么发生器和
在哪个星系被要求。这就是“随机”这个词的意思
毕竟——不可预测且独立于任何事物——
否则它不再是随机的,对吧?凭着这种直觉
很自然地在网上搜索一些魔法咒语来获得
任何可能的上下文中的此类随机数。
^^^ 在涉及Pseudo-Random Number Generators 的所有情况下,这种直观的期望都是非常错误和有害的 - 尽管对于真正的随机数是合理的。
虽然“随机数”这个有意义的概念存在(有点)——但不存在“伪随机数”之类的东西。 Pseudo-Random Number Generator 实际上会产生伪随机数序列。
伪随机序列实际上总是确定性(由其算法和初始参数预先确定)——也就是说,它实际上没有任何随机性。
当专家谈论 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中定义的per-object伪随机数生成器标准。使用相同的模板参数和相同的初始化参数,不同的对象将在使用符合 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;
}