繁体   English   中英

为什么这个随机数生成器生成相同的数字?

[英]Why is this random number generator generating same numbers?

第一个起作用,但是第二个总是返回相同的值。 为什么会发生这种情况,我应该如何解决呢?

int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<> dis(0, 1);

    for(int i = 0; i < 10; i++) {

        std::cout << dis(gen) << std::endl;
    }return 0;
}

一个不起作用:

double generateRandomNumber() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<> dis(0, 1);

    return dis(gen);
}



int main() {
    for(int i = 0; i < 10; i++) {
        std::cout << generateRandomNumber() << std::endl;
    }return 0;
}

您在哪个平台上工作? 如果不存在用于生成随机数的硬件或操作系统功能,则允许std::random_device为伪RNG。 它可能使用当前时间进行初始化,在这种情况下,您所调用的时间间隔可能相隔太近,以至于“当前时间”无法采用其他值。

但是,正如评论中提到的那样,并不意味着要使用这种方式。 一个简单的解决方法是将rdgen声明为static 适当的解决方法是将RNG的初始化移出需要随机数的函数,因此也可以将其用于需要随机数的其他函数。

第一个为所有数字使用相同的生成器,第二个为每个数字创建一个新的生成器。

让我们比较一下这两种情况之间的差异,看看为什么会发生这种情况。


情况1:

 int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); for(int i = 0; i < 10; i++) { std::cout << dis(gen) << std::endl; }return 0; } 

在您的第一种情况下,程序将执行main函数,并且在此发生的第一件事是您在堆栈上创建了一个std::random_devicestd::mt19337std::uniform_real_distribution<>的实例,这些实例属于main()的范围。 您的mersenne twister gen将根据随机设备rd的结果进行一次初始化。 您还初始化了分布dis以使其值的范围为01 每次运行该应用程序时,它们仅存在一次。

现在,您创建一个for循环,该循环从索引0开始并递增到9并且在每次迭代中,您都将使用分配disoperator()()将已播种的gen传递给它,从而将结果显示给cout 每次在该循环上, dis(gen)都会产生不同的值,因为gen已经仅被播种了一次。


情况2:

 double generateRandomNumber() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); return dis(gen); } int main() { for(int i = 0; i < 10; i++) { std::cout << generateRandomNumber() << std::endl; }return 0; } 

在此版本的代码中,让我们看一下相似之处和不同之处。 在这里,程序执行并进入main()函数。 这次,它遇到的第一件事是从09的for循环,类似于上面的main,但是此循环是main堆栈上的第一件事。 然后调用cout来显示user defined function generateRandomNumber() 该函数总共被调用10次,每次您遍历for循环时,此函数都有其自己的堆栈存储器,该堆栈存储器将被缠绕,展开或创建和销毁。

现在,让我们跳入这个名为generateRandomNumber() user defined function

该代码看起来与之前直接位于main()的代码几乎完全一样,但是这些变量位于generateRandomNumber()的堆栈中,并具有其作用域的寿命。 每当此函数进入和超出范围时,将创建和销毁这些变量。 这里的另一个区别是此函数还返回dis(gen)

注意:我不确定100%是否会返回copy或者编译器最终是否会进行某种优化,但是按值返回通常会生成副本。

最终,当函数generateRandomNumber()返回并且在它完全超出作用域之前,在其中std::uniform_real_distribrution<>operator()()被调用,并且进入返回到自己的堆栈和作用域,然后返回主generateRandomNumber()进行简短介绍,然后返回主目录。


-可视化差异-

如您所见,这两个程序是完全不同的,确切地说是非常不同的。 如果您想获得更直观的证据来证明它们是不同的,可以使用任何可用的在线编译器将每个程序输入到它在assembly向您显示该程序的位置,并比较两个汇编版本以查看它们的最终区别。

可视化这两个程序之间差异的另一种方法不仅是查看它们的assembly等效项,还包括使用debugger逐行浏览每个程序,并关注stack calls以及它们的缠绕和展开,并关注所有值初始化,返回和销毁。


-评估与推理-

第一个按预期工作的原因是因为您的random devicegenerator和您的distribution都具有main生命周期,并且您的generator仅使用随机设备播种一次,并且每次仅使用一个发行版for循环。

在您的第二个版本中,main对此一无所知,它只知道它正在经历for循环并将返回的数据从用户函数发送到cout。 现在,每当它通过for循环时,都会调用此函数,并且每次创建和销毁它时都按我说的那样堆栈,因此所有其变量都将被创建和销毁。 因此,在此实例中,您将创建并销毁10: rdgen(rd())dis(0,1)实例。


-结论-

除了我上面已经描述的以外,还有更多的内容,而与您的随机数生成器的行为有关的另一部分是用户Kane在对您的问题的评论中对您的陈述中提到的内容:

来自en.cppreference.com/w/cpp/numeric/random/random_device :“ std :: random_device可以根据实现定义的伪随机数引擎来实现。在这种情况下,每个std ::: random_device对象可以生成相同的数字序列。”

每次创建和销毁时,都会使用新的random_devicegenerator种子,但是,如果您的特定机器或操作系统不支持使用random_device ,则可以最终使用某个任意值作为其种子值,或者可以最终使用系统时钟生成种子值。

因此,可以说它确实使用系统时钟结束, main()的for循环的执行发生得如此之快,以至于10调用generateRandomNumber()所完成的所有工作已经在几毫秒之前完成了。通过了。 因此,此处的增量时间最小且可忽略不计,因为它在每次通过时都生成相同的种子值,并且从分布中生成相同的值。

请注意, std::mt19937 gen(rd())非常有问题。 看到这个问题 ,它说:

  • rd()返回单个unsigned int 它至少有16位,大概是32位。这还不足以播种[此生成器的巨大状态]。
  • 使用std::mt19937 gen(rd());gen() (以32位播种并查看第一个输出)不能提供良好的输出分布。 7和13永远不会是第一个输出。 两颗种子产生0。十二颗种子产生1226181350。( 链接
  • std::random_device可以(有时被实现为带有固定种子的简单PRNG)。 因此,每次运行可能会产生相同的序列。 链接

此外, random_device的用于生成“不确定性”随机数的方法是“实现定义的”,并且random_device如果实现由于“实现限制”而无法生成“不确定性”随机数, random_device允许实现“使用随机数引擎”。 ([rand.device])。 (例如,在C ++标准下,实现可能使用来自系统时钟的时间戳或使用快速移动的周期计数器来实现random_device ,因为两者都是不确定的。)

应用程序不应盲目调用random_device的生成器( rd() ),而至少也不要调用entropy()方法,该方法以位为单位对实现的熵进行估算。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM