【问题标题】:C++ generating random numbers in a loop using default_random_engineC++ 使用 default_random_engine 在循环中生成随机数
【发布时间】:2015-10-14 17:23:40
【问题描述】:

最近我正在尝试使用#include<random> 中定义的random engines 在C++ 中生成随机数的程序。我的程序如下:-

#include <iostream>
#include <random>
#include <chrono>
using namespace std;
int random (int lim)
{
    default_random_engine dre (chrono::steady_clock::now().time_since_epoch().count());
    uniform_int_distribution<> uid(1,lim);
    return uid(dre);
}
int main()
{
    for (int i=0;i<10;++i)
    cout<<random(100)<<" ";
    return 0;
}

简单,绝对!但是当我尝试输出时,数字不像这些那样随机:-

66 95 95 96 96 96 96 96 97 97

当我稍微更改我的程序并将default_random_engine 声明为static 或将其设置为global 时,我的输出是正确的,例如:-

62 53 21 38 7 51 46 40 86 12

谁能指出我的程序最初出了什么问题?小改动如何帮助我获得更好的输出?

【问题讨论】:

    标签: c++ random


    【解决方案1】:

    每次运行 random 时,您都会使用相同或非常接近相同的种子重新播种生成器,这样每次迭代都会获得相同的输出,或者即使它确实发生了变化,也不会改变太多。

    要解决此问题,您只需为生成器播种一次,然后继续调用它。通过将其设置为静态,它可以按预期工作,因为生成器仅在您继续执行其随机序列而不是获取它将创建的第一个随机数时才创建和播种。

    通常,随机数生成器有一个内部值,它们通过算法传递,算法吐出的是它们为随机数返回的值。然后将该数字保留用于算法的下一次迭代。这就是我们获得随机序列的方式。

    如果我们使用相同的种子,我们将得到相同的输出序列,因为我们从相同的数字开始。在您的情况下,您的种子只会在每次 chrono::steady_clock::now() 前进时发生变化,并且您的循环运行速度比这更快,因此您每次调用都获得相同的时间(种子)。

    【讨论】:

    • 无论如何调用它一次有什么帮助,毕竟我会一次又一次地调用循环,你的逻辑适用于第一种情况,但为什么 STATIC 情况很好
    • “非常接近的种子”不应该产生相同的输出,这里的问题是使用相同的种子值重复播种。通过切换到high_resolution_clock 而不是steady_clock,OP 可能会获得更好的结果。当然,更好的解决方法是不要按照您的建议继续重新播种。
    • @Praetorian "high_resolution_clock" 也不能正常工作
    • @Anwesha:可能分辨率不够高,毕竟你是在一个紧密的循环中调用它,每次调用之间可能有 100 条指令,所以它要么使用 rdtsc,要么每次都会重新播种具有相同的价值。但正如已经解释过的那样:您必须只播种一次,然后继续使用相同的随机引擎对象。
    • 我想重申“非常接近的种子”不是问题。种子加一应该产生看起来完全独立的序列。 (当然,这并不意味着时间是种子数据的唯一来源。)
    【解决方案2】:

    关于伪随机数生成器需要了解的重要一点是,它们会生成一个数字序列,而“随机性”是 序列 所测试的质量。也就是说,你不能说一个特定的数字是随机的还是不是随机的 (is '4' a random number?)。而是说数字序列是否是随机的。

    当您“播种”一个 pRNG 时,您通常会选择它可以生成的序列之一,并且这些数字相对于序列的其余部分是随机的。所以一般来说,如果你想要“随机性”,你首先要选择一个特定的序列,然后使用该特定随机序列中的连续数字。

    在没有static 的代码中,您在循环的每次迭代中选择一个序列,然后使用该序列中的一个数字。由于您没有使用来自同一序列的许多值,因此结果看起来不是随机的也就不足为奇了。

    当您添加 static 时,引擎不再在循环的每次迭代中播种。相反,它只播种一次,循环的每次迭代都使用该序列中的连续值,就像你应该做的那样。这就是static 对块作用域变量的含义:这意味着该变量存在于它声明的块的外部,并且只会在第一次初始化。

    static 确实解决了您的问题,但我认为这不是一个好的解决方案。 static 变量与全局变量有一些相似之处,并导致一些相同的问题。相反,我建议您将引擎显式移出循环。例如:

    int random (int lim, default_random_engine &dre)
    {
        uniform_int_distribution<> uid(1,lim);
        return uid(dre);
    }
    
    int main()
    {
        default_random_engine dre (chrono::steady_clock::now().time_since_epoch().count());
    
        for (int i = 0; i < 10; ++i)
        {
            cout << random(100, dre) << " ";
        }
    }
    

    现在状态被显式管理并传递给random()函数,这比依赖static变量的隐藏和隐含行为要好。


    一般来说,我不建议将时间用作种子数据的唯一来源。低分辨率时钟可能无法足够频繁地提供新种子,同时在不同地方运行的程序可以使用相同的种子。

    我对播种的建议是:

    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    std::mt19937 eng(seed);
    

    (初学者不需要知道 mt19937 或任何其他位是关于什么的。他们只需要知道将其粘贴到适当的位置以及如何使用 eng。在您的代码中,您需要将default_random_engine 的每次使用替换为mt19937。)

    【讨论】:

      猜你喜欢
      • 2014-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-22
      • 2011-02-12
      • 1970-01-01
      相关资源
      最近更新 更多