如何不止一次地调用srand会影响随机性的质量?

时间:2014-11-30 02:10:54

标签: c++ random srand

comment,其中声明:

  

srand(time(0));我会将此行作为main()中的第一行   相反,如果多次调用它(实际上会导致更少   随机数)。

...而且我已经加粗了我遇到问题的问题...重复一次在程序中调用srand的常见建议。诸如srand() — why call only once?之类的问题会重复,因为time(0)以秒为单位返回当前时间,在同一秒内多次调用srand会生成相同的种子。常见的解决方法是使用毫秒或纳秒。

但是,我不明白为什么这意味着srand应该或只能被调用一次,或者它如何导致更少的随机数字。

cppreference

  

一般来说,伪随机数发生器应该只是   在对rand()的任何调用和程序的开始之前播种一次。   每次您希望生成一批新的伪随机数时,不应重复播种或重新播种。

phoxissrand() — why call only once?的回答:

  

初始化一旦具有种子值的初始状态将生成   足够的随机数,因为你没有用srand设置内部状态,   因此使得数字更可能是随机的。

也许他们只是使用不精确的语言,没有一个解释似乎解释了为什么多次调用srand是坏的(除了产生相同的随机数序列)或它如何影响“随机性”的数字。有人可以为我清楚这一点吗?

6 个答案:

答案 0 :(得分:5)

std::time(0)函数以返回时间。

问题是计算机速度如此之快,如果每次要求随机数{{1}时调用std::time(0)std::srand()函数在调用std::srand()之间就不会发生变化将继续被重置以产生相同的数字序列,直到std::srand()函数返回不同的时间(一秒钟后)。

一秒中,您最终可能会生成相同数量的数百万次!那不是随机

答案 1 :(得分:3)

从这个问题中查看srand()的来源:Rand Implementation

此外,此线程的示例实现:

static unsigned long int next = 1;

int rand(void) // RAND_MAX assumed to be 32767
{
    next = next * 1103515245 + 12345;
    return (unsigned int)(next/65536) % 32768;
}

void srand(unsigned int seed)
{
    next = seed;
}

正如您所看到的,当您致电srand(time(0))时,rand()上的新号码取决于种子。数字会在一些数百万之后重复,但再次调用srand会使其他数字变为另一个数字。无论如何,它必须在一些周期后重复 - 但是顺序取决于srand的参数。这就是为什么C rand对加密没有好处 - 你可以在知道种子时预测下一个数字。

如果你有快速循环,那么每次迭代调用srand都是没有意义的 - 你可以得到相同的数字,而你的time()(1秒是现代CPU的非常大的时间)给另一个种子。

简单的应用程序没有理由多次调用srand - 这个生成器设计很弱,如果你想要真正的随机数,你必须使用其他(我知道最好的是 Blum Blum Shub

对我来说,没有或多或少的随机数 - 它总是取决于种子,如果你使用相同的种子,它们会重复。使用时间是一个很好的解决方案,因为它易于实现,但您必须只使用一个(在main()开头)或当您确定在另一秒内调用srand(time(0))时。

答案 2 :(得分:2)

伪随机生成器是一种引起几乎随机的数字的引擎。但是,它们完全是确定性的。换句话说,给定种子x0,它们是通过在x0上重复应用某些内射函数生成的,将其称为f(x0),以便f^m(x0)f^{m-1}(x0)完全不同{1}}或f^{m+1}(x0),其中符号f^m表示函数组合m次。换句话说,f(x)有巨大的跳跃,几乎与之前的不相关。

如果您在一秒钟内多次使用sradnd(time),则可能会获得相同的种子,因为时钟没有您想象的那么快。因此,随机数的结果序列将是相同的。这可能是一个(巨大的)问题,特别是在密码学应用中(无论如何,在后一种情况下,人们购买基于实时物理过程的良好数字生成器,如大气数据中的温差等,或者,最近,测量量子比特,例如偏振光子的叠加,后者是真正随机的,只要量子力学是正确的。)

rand还存在其他严重问题。其中之一是分布有偏见。参见例如http://eternallyconfuzzled.com/arts/jsw_art_rand.aspx进行了一些讨论,虽然我记得我在SO上看过类似的东西,但现在找不到它。

如果您计划在加密应用程序中使用它,请不要这样做。使用<random>和一个严重的随机引擎,如Mersene的twister std::mt19937结合std::random_device

如果使用srand为随机数生成器播种两次,并获得不同的种子,那么 你会得到两个完全不同的序列。这对您来说可能是令人满意的。但是,由于上面提到的问题,每个序列本身不会是一个好的随机分布。另一方面,如果你对你的rng播种次数太多,你会得到相同的种子,而且这很糟糕,因为你会一遍又一遍地生成相同的数字。

PS:在评论中看到伪数取决于种子,这很糟糕。这是伪数的定义,并且它不是坏事,因为它允许您使用相同的序列重复数值实验。这个想法是每个不同的种子应该产生一系列(几乎)随机数,与之前的序列不同(从技术上讲,你不应该将它们与完美的随机序列区分开来)。

答案 3 :(得分:2)

数字rand()返回实际上不是随机的,而是&#34;伪随机的。&#34;这意味着rand()生成一个数字流,这些数字随机地显示给定的值&#34; look&#34;和&#34;随机&#34;来自内部状态,随着每次调用而变化。

通常,rand()是所谓的线性全等生成器,这意味着使用大致相同的机制:

int state; // persistent state

int rand() {
  state = (a * state + b) % c;
  return state;
}

精心挑选的常量abcc在实践中往往是两个人的力量,因为这使计算速度更快。

&#34;随机性&#34;这个序列的部分取决于国家的持久性。如果序列始终以可预测的值重新接种,则rand()的返回值将依次变为可预测。这有多重要取决于应用,但它不是纯粹的学术考虑。例如,考虑案例

a = 69069
b = 1
c = 2^32

例如,旧版本的glibc使用了它。虽然我选择了这个例子来说明模式的显而易见性,但重点仍然是不太明显的情况。想象一下,这个RNG播种了一系列递增数n,n + 1,n + 2等等 - 你将从rand()获得一个数字序列,每个数字比最后一个大69069(模2 ^ 32 )。图案清晰可见。从0开始,我们将得到

1
69070
138139
207208
...

以稳定的增量上升到超过40亿。更糟糕的是,在调用rand之后,某些实现实际上在srand的第一次调用中返回了种子值,在这种情况下,您只需要取回种子。

答案 4 :(得分:1)

种子确定将生成哪些随机数,按顺序,即srand(1),将始终在第一次调用rand()时生成相同的数字,第二次调用{{1}时相同等等。

换句话说,如果您在每次rand()调用之前重新播种相同的种子,则每次都会生成相同的随机数。

因此,在一秒钟内使用rand()连续播种将意味着重播后的所有随机数实际上都是相同的数字。

答案 5 :(得分:0)

大多数其他答案都在准确地说明已经说明的问题:以相同的秒多次调用srand将产生相同的种子。我相信 actual 问题与我遇到的问题相同,即:为什么多次调用srand会很不好,即使每次都使用不同的种子?< / p>

我可以想到三个原因:

  1. 人们的语言不清晰,实际上意味着如果您想要不同的随机数序列,srand不应多次与time()一起调用。

  2. 从密码学上讲,这是不好的,因为传递给srand的每个种子本身都不是随机数(嗯,可能不是)。意思是,每个srand都为某人注入了一个机会来猜测该种子,从而预测了您的伪随机数流。

  3. 它可以弄乱伪随机数的分布。 @vsoftco的回答给了我一个线索。如果您一次调用srand,则可以设计rand使您在整个生存期内均匀地分配伪随机数。但是,如果在中间调用srand,则将放弃该均匀分布,因为它将以新种子“重新开始”。

因此,如果您不关心其中的任何一个,我认为可以多次致电srand。就我而言,我想在程序开始时调用它,但是在fork()之后再次调用它,因为种子显然在子进程之间共享,并且我希望每个子进程都有自己的伪序列。 -随机数。


回到为什么它在密码上不好的原因,如果它是time()之类的东西,就更容易猜出种子,因为坏演员可以尝试猜测它的种子时间。这就是为什么在程序开始时调用srand可能会更好的原因,因为某人猜测该时间以及发起服务器请求的时间不太可能。

但是我推测,如果基础时钟可能没有这种精度,那么即使经过了十亿分之一秒也将是密码学上的危险。例如,假设您调用srand(get_time_in_ns()),而底层时钟仅将时间返回到最近的毫秒。

现在,我在任何方面都不是加密专家,但这使我想知道将不同伪随机生成器的输出作为种子传递是否比当前安全?多个srand通话?例如,您能否使用Linux的srand中的号码呼叫每个/dev/random? (我想如果您想获得比当前时间更安全的种子,但仍想使用rand(),那么您就不必每次都从内核读取内容了。)