【问题标题】:How does calling srand more than once affect the quality of randomness?多次调用 srand 如何影响随机性的质量?
【发布时间】:2014-11-30 02:10:54
【问题描述】:

这个comment,声明:

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

...我已经将我遇到问题的行加粗了...重复常见的建议,在程序中调用一次srand。像srand() — why call only once? 这样的问题重申,因为time(0) 以秒为单位返回当前时间,所以在同一秒内多次调用srand 将产生相同的种子。一种常见的解决方法是改用毫秒或纳秒。

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

cppreference:

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

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

初始化一次,初始状态会产生种子值 足够的随机数,因为您没有使用 srand 设置内部状态, 从而使数字更有可能是随机的。

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

【问题讨论】:

  • 本身:不。提供上下文,否则我们无法判断质量受到的影响程度。
  • @Columbo 对于严重使用,srand 存在多个问题。其中之一是分布有偏差。参见例如eternallyconfuzzled.com/arts/jsw_art_rand.aspx 进行一些讨论,虽然我记得我在 SO 上看到过类似的东西,虽然现在找不到。
  • @vsoftco 不相关?我们谈论的是质量的差异,而不是质量本身。
  • @Columbo 我提到偏向低位是一个问题,在我看来是最严重的问题。这是质量上的差异,并链接了一个页面,这在我的帖子中解释得很长。换句话说,源的熵与H(|X|) 相差甚远,|X| 是字母表的大小。
  • @vsoftco 我们必须案例:种子兰特一次,种子兰特几次。现在我们担心生成的序列的质量差异。您以任何方式链接的页面与此问题有什么关系?

标签: c++ random srand


【解决方案1】:

从这个问题看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()(对于现代 CPU 来说,1 秒是非常大的时间)提供另一个种子。

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

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

【讨论】:

    【解决方案2】:

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

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

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

    如果您打算在加密应用程序中使用它,请不要这样做。使用 <random> 和一个严肃的随机引擎,比如 Mersene 的捻线器 std::mt19937 结合 std::random_device

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

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

    【讨论】:

    • srand 没有严重问题。 rand 存在严重问题,但这与它的播种无关。
    • @Columbo,对不起,是的,我的意思是rand,播种后会更新。好的,更新了,希望我能澄清一下这个问题。无论如何,感谢您的提问,因为它让我思考更多并给出更好的答案。
    【解决方案3】:

    rand() 返回的数字实际上不是随机的,而是“伪随机的”。这意味着rand() 生成一个数字流,对于给定的“look”和“random”值,这些数字看起来是随机的,内部状态会随着每次调用而变化。

    通常,rand() 是所谓的线性同余生成器,这意味着它使用的机制大致如下:

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

    使用精心挑选的常量abcc 在实践中往往是 2 的幂,因为这样可以加快计算速度。

    这个序列的“随机性”部分取决于状态的持久性。如果序列不断用可预测的值重新播种,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 亿。更糟糕的是,在调用 srand 之后,某些实现实际上在 rand 的第一次调用中返回了种子值,在这种情况下,您只需取回种子。

    【讨论】:

      【解决方案4】:

      种子决定将生成哪些随机数,即srand(1),在第一次调用rand()时总是生成相同的数字,在第二次调用rand()时生成相同的数字,依此类推。

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

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

      【讨论】:

        【解决方案5】:

        大多数其他答案都在说明问题已经说明的内容:在同一秒内多次调用srand 将产生相同的种子。我相信 实际 问题与我的问题相同,即:为什么多次调用 srand 会很糟糕,即使每次都使用不同的种子?

        我能想到三个原因:

        1. 人们不清楚他们的语言,他们实际上的意思是,如果您想要不同的随机数序列,则不应使用 time() 多次调用 srand

        2. 这在密码学上很糟糕,因为传递给srand 的每个种子本身并不是一个随机数(嗯,可能不是)。这意味着,每个srand 都在为某人提供猜测该种子的机会,从而预测您的伪随机数流。

        3. 它会弄乱伪随机数的分布。 @vsoftco 的回答给了我一个线索。如果您调用一次srandrand 可以设计为在其生命周期内为您提供伪随机数的均匀分布。但是,如果您在中间调用 srand,您将抛弃这种均匀分布,因为它会以新种子“重新开始”。

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


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

        但我推测,如果底层时钟有可能没有那种精度,那么即使经过纳秒也会在密码学上很危险。例如,假设您调用 srand(get_time_in_ns()),而底层时钟仅将时间返回到最接近的毫秒。

        现在,我无论如何都不是加密专家,但这让我想知道将 不同 伪随机生成器的输出作为种子传递是否比当前时间更安全多个srand 电话?例如,您可以使用 Linux 的 /dev/random 中的号码来调用每个 srand 吗? (我想如果您想要一个比当前时间更安全的种子但仍想使用rand(),那么您可能想要这样做,这样您就不必每次都从内核读取。)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-10
          • 2013-04-21
          • 1970-01-01
          • 2015-04-20
          • 2018-04-03
          • 2012-06-18
          相关资源
          最近更新 更多