使用C++11随机库生成随机数

172

正如标题所示,我正在尝试使用新的C++11 <random>库生成随机数。我已经尝试了以下代码:

std::default_random_engine generator;
std::uniform_real_distribution<double> uniform_distance(1, 10.001);

我写的代码有个问题,每次编译运行后生成的数字都是一样的。那么我的问题是,随机库中还有哪些函数可以产生真正随机的数值。

特别地,我需要获取一个在区间[1, 10]内的随机数。


3
这个问题很危险,因为它涉及到“主观性”。如果你可以消除关于观点的请求,我认为这个问题会非常有用(如果还没有被提出的话)。 - John Dibling
5
除非你有充分的理由,否则我建议使用std::mt19937作为引擎。这个分布是一个双端封闭区间。 - chris
6
请查看以下链接:https://dev59.com/PGw05IYBdhLWcg3wpTYV,https://dev59.com/FmQn5IYBdhLWcg3w1aBf,https://dev59.com/D2gt5IYBdhLWcg3wygab和http://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful。 - dyp
2
@chris 分布在两端不是封闭的,请查看此链接或此链接 - memo1288
1
@memo1288,谢谢你。我以为原帖中使用的是std::uniform_int_distribution,它在两端都是闭区间。 - chris
显示剩余3条评论
6个回答

255
微软的Stephan T. Lavavej(stl)在Going Native上做了一个关于如何使用新的C++11随机函数以及为什么不要使用rand()的演讲。在演讲中,他包含了一张幻灯片,基本上解决了你的问题。我将下面的代码从那张幻灯片上复制了出来。
你可以在这里看到他完整的演讲here
#include <random>
#include <iostream>

int main() {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<double> dist(1.0, 10.0);

    for (int i=0; i<16; ++i)
        std::cout << dist(mt) << "\n";
}

我们使用random_device一次来为名为mt的随机数生成器提供种子。 random_device()mt19937慢,但它不需要种子,因为它从操作系统请求随机数据(例如,可以从各种位置获取,如RdRand)。
看了一下这个问题/答案,似乎uniform_real_distribution返回的是一个在区间[a, b)内的数值,而你想要的是[a, b]。为了实现这个目标,我们的uniform_real_distribution应该像这样:
std::uniform_real_distribution<double> dist(1, std::nextafter(10, DBL_MAX));

3
由于问题要求使用最常见的方法生成随机数,你可以使用default_random_engine。根据C++ Primer,它是实现中被认为最有用的引擎。 - aaronman
3
@aaronman说他参考了STL的谈话,STL明确表示不喜欢default_random_engine存在。 - Bill Lynch
5
我们都知道向量(vector)和映射(map)的区别,但不是每个人都知道mt19937和ranlux24之间的区别。如果有人能够成为程序员却不知道什么是向量和字典,也许他们应该使用std::default_container。希望没有人认为自己是程序员而不知道区别。许多脚本语言都有默认的映射类型结构,这可能是以各种方式实现的,用户可能不知道。 - aaronman
23
对于大多数应用程序而言,nextafter 函数调用是过度的。一个随机的 double 值恰好落在端点上的概率非常小,因此包括或排除它没有实际区别。 - Mark Ransom
4
@chris 与此无关(但你已打开了话题),你的 std::vector 比喻在这里不适用,因为由于 CPU 缓存作用,std::vector 实际上是一个很好的默认选择。即使你理解所有容器并且可以根据算法复杂度做出明智的决策,它甚至比中间插入性能更好的 std::list 更优秀。 - void.pointer
显示剩余11条评论

33

我的 "random" 库提供了一个方便的封装,可以简化 C++11 随机数类的使用。你可以通过一个简单的 "get" 方法完成几乎所有操作。

例如:

  1. 获取指定范围内的随机数

auto val = Random::get(-10, 10); // Integer
auto val = Random::get(10.f, -10.f); // Float point
  • 随机布尔值

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
    
  • 从 std::initializer_list 中获取随机值

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
    
  • 从迭代器范围或整个容器中获取随机迭代器

  • auto it = Random::get( vec.begin(), vec.end() ); // it = random iterator
    auto it = Random::get( vec ); // return random iterator
    

    还有更多的东西!请查看 GitHub 页面:

    https://github.com/effolkronium/random


    11
    我阅读了上面的所有内容,还有与其中包含c++相关的其他大约40页,例如这个,并观看了Stephan T. Lavavej "STL"的视频,但仍然不确定随机数如何在实践中起作用,所以我花了整个星期天来了解它是关于什么以及如何使用它。
    我认为STL关于“不再使用srand”的看法是正确的,并且他在2中的视频中也很好地解释了这一点。他还建议使用:
    a) void random_device_uniform() -- 用于加密生成但速度较慢(来自我的示例)
    b) 带有mt19937的示例 -- 更快,能够创建种子,但未加密
    我拿出了我可以访问到的所有声称支持c++11的书籍,并发现像Breymann(2015)这样的德国作者仍在使用克隆版。
    srand( time( 0 ) );
    srand( static_cast<unsigned int>(time(nullptr))); or
    srand( static_cast<unsigned int>(time(NULL))); or
    

    只需使用<random>而不是<time>和<cstdlib> #includings - 所以要小心只从一本书中学习 :)。

    这意味着自C ++11起不应该使用它,因为:

    程序通常需要一个随机数源。在新标准之前,C和C ++都依赖于一个简单的C库函数rand。该函数生成伪随机整数,这些整数均匀分布在0到至少32767个系统相关最大值的范围内。rand函数有几个问题:许多程序(如果不是大多数)需要与rand生成不同范围内的随机数。一些应用程序需要随机浮点数。一些程序需要反映非均匀分布的数字。当程序员尝试转换由rand生成的数字的范围、类型或分布时,他们经常引入非随机性。(引自Lippmans C++ primer fifth edition 2012)


    我终于在Bjarne Stroustrups的一些新书中找到了最好的解释。

    其实很简单,因为随机数生成器由两部分组成: (1)生成随机或伪随机值序列的引擎。 (2)将这些值映射到范围内的数学分布的分布。


    尽管微软STL的人有不同意见,但Bjarne Stroustrups写道:

    在C++中,标准库提供了随机数引擎和分布(§24.7)。默认情况下使用default_random_engine,该引擎被选择为具有广泛适用性和低成本。

    void die_roll()示例来自Bjarne Stroustrups - 用using生成引擎和分布是个好主意(更多信息请参见这里)。


    为了能够实际利用标准库提供的随机数生成器<random>,这里提供一些可执行的代码,其中包含不同的示例,缩小到最少必要的内容,希望能为大家节省时间和金钱:

        #include <random>     //random engine, random distribution
        #include <iostream>   //cout
        #include <functional> //to use bind
    
        using namespace std;
    
    
        void space() //for visibility reasons if you execute the stuff
        {
           cout << "\n" << endl;
           for (int i = 0; i < 20; ++i)
           cout << "###";
           cout << "\n" << endl;
        }
    
        void uniform_default()
        {
        // uniformly distributed from 0 to 6 inclusive
            uniform_int_distribution<size_t> u (0, 6);
            default_random_engine e;  // generates unsigned random integers
    
        for (size_t i = 0; i < 10; ++i)
            // u uses e as a source of numbers
            // each call returns a uniformly distributed value in the specified range
            cout << u(e) << " ";
        }
    
        void random_device_uniform()
        {
             space();
             cout << "random device & uniform_int_distribution" << endl;
    
             random_device engn;
             uniform_int_distribution<size_t> dist(1, 6);
    
             for (int i=0; i<10; ++i)
             cout << dist(engn) << ' ';
        }
    
        void die_roll()
        {
            space();
            cout << "default_random_engine and Uniform_int_distribution" << endl;
    
        using my_engine = default_random_engine;
        using my_distribution = uniform_int_distribution<size_t>;
    
            my_engine rd {};
            my_distribution one_to_six {1, 6};
    
            auto die = bind(one_to_six,rd); // the default engine    for (int i = 0; i<10; ++i)
    
            for (int i = 0; i <10; ++i)
            cout << die() << ' ';
    
        }
    
    
        void uniform_default_int()
        {
           space();
           cout << "uniform default int" << endl;
    
           default_random_engine engn;
           uniform_int_distribution<size_t> dist(1, 6);
    
            for (int i = 0; i<10; ++i)
            cout << dist(engn) << ' ';
        }
    
        void mersenne_twister_engine_seed()
        {
            space();
            cout << "mersenne twister engine with seed 1234" << endl;
    
            //mt19937 dist (1234);  //for 32 bit systems
            mt19937_64 dist (1234); //for 64 bit systems
    
            for (int i = 0; i<10; ++i)
            cout << dist() << ' ';
        }
    
    
        void random_seed_mt19937_2()
        {
            space();
            cout << "mersenne twister split up in two with seed 1234" << endl;
    
            mt19937 dist(1234);
            mt19937 engn(dist);
    
            for (int i = 0; i < 10; ++i)
            cout << dist() << ' ';
    
            cout << endl;
    
            for (int j = 0; j < 10; ++j)
            cout << engn() << ' ';
        }
    
    
    
        int main()
        {
                uniform_default(); 
                random_device_uniform();
                die_roll();
                random_device_uniform();
                mersenne_twister_engine_seed();
                random_seed_mt19937_2();
            return 0;
        }
    

    我认为这总结了所有内容,就像我所说的,我花费了很多时间来阅读和筛选出这些例子 - 如果你有更多关于数字生成的信息,欢迎私信或在评论区留言告诉我,如果有必要我会添加或编辑这篇文章。Bool


    0
    这里有一些关于伪随机数生成器的资源,你可以阅读。

    https://en.wikipedia.org/wiki/Pseudorandom_number_generator

    基本上,计算机中的随机数需要一个种子(这个数字可以是当前系统时间)。
    替换
    std::default_random_engine generator;
    

    通过

    std::default_random_engine generator(<some seed number>);
    

    0

    这是我刚写的一些相关内容:

    #include <random>
    #include <chrono>
    #include <thread>
    
    using namespace std;
    
    //==============================================================
    // RANDOM BACKOFF TIME
    //==============================================================
    class backoff_time_t {
      public:
        random_device                      rd;
        mt19937                            mt;
        uniform_real_distribution<double>  dist;
    
        backoff_time_t() : rd{}, mt{rd()}, dist{0.5, 1.5} {}
    
        double rand() {
          return dist(mt);
        }
    };
    
    thread_local backoff_time_t backoff_time;
    
    
    int main(int argc, char** argv) {
       double x1 = backoff_time.rand();
       double x2 = backoff_time.rand();
       double x3 = backoff_time.rand();
       double x4 = backoff_time.rand();
       return 0;
    }
    

    ~


    -4
    你有两种常见情况。第一种是需要随机数,并且并不太关心其质量或执行速度。在这种情况下,请使用以下宏。
    #define uniform() (rand()/(RAND_MAX + 1.0))
    

    这段代码可以给你一个在0到1-epsilon范围内的p值(除非RAND_MAX比double精度还大,但是遇到这种情况再考虑)。

    int x = (int) (uniform() * N);

    现在可以得到一个0到N-1的随机整数。

    如果你需要其他分布,就必须对p进行转换。或者有时候调用uniform()多次会更容易实现。

    如果你想要可重复的行为,就使用一个常量作为种子,否则就使用time()函数的返回值作为种子。

    如果你关心质量或运行时间性能,可以重写uniform()函数。但是如果不关心,就不要修改代码。始终将uniform()函数保持在0到1-epsilon之间。现在你可以包装C++随机数库来创建更好的uniform()函数,但这是一种中级选项。如果你关心RNG的特性,那么值得花费一些时间了解底层方法的工作原理,然后提供一个自己的函数。这样你就完全控制了代码,并且可以保证在相同的种子下,序列始终完全相同,无论平台或链接到哪个版本的C++。


    3
    但这并不是均匀的(0到N-1)。原因很简单,假设N=100,RAND_MAX = 32758。没有一种方法可以将32758个元素(RAND_MAX)均匀地映射到100个输入。唯一的方法是设置32000的边界,并在超出边界时重新执行rand()函数。 - amchacon
    2
    如果N为100,则您的随机数生成器必须非常好才能检测到与平坦分布的偏差。 - Malcolm McLean

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接