Генерация случайных чисел с использованием случайной библиотеки C ++ 11


135

Как следует из названия, я пытаюсь найти способ генерирования случайных чисел с помощью новой <random>библиотеки C ++ 11 . Я пробовал это с этим кодом:

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

Проблема с кодом, который у меня есть, заключается в том, что каждый раз, когда я компилирую и запускаю его, он всегда генерирует одинаковые числа. Итак, мой вопрос: какие другие функции в случайной библиотеке могут выполнить это, будучи действительно случайным?

Для моего конкретного случая использования я пытался получить значение в диапазоне [1, 10]


3
Этот вопрос опасно граничит с «главным образом на основе мнений». Если вы можете избавиться от просьбы о мнении, я вижу этот вопрос очень полезным (если он еще не задавался).
Джон Диблинг

4
Я предлагаю использовать в std::mt19937качестве двигателя, если у вас нет веских причин не делать этого. И распределение это закрытый интервал с обоих концов.
Крис


2
@ Крис Дистрибуция не закрыта с обеих сторон, проверьте эту ссылку или эту ссылку
memo1288

1
@ memo1288, спасибо, я думал , что ОП было использование std::uniform_int_distribution, который будет закрыт на обоих концах.
Крис

Ответы:


191

Стефан Т. Лававей (stl) из Microsoft выступил в Going Native с докладом о том, как использовать новые случайные функции C ++ 11 и почему их не использовать rand(). В него он включил слайд, который в основном решает ваш вопрос. Я скопировал код из этого слайда ниже.

Вы можете увидеть его полный доклад здесь: http://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

#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_distibutionдолжен выглядеть так:

std::uniform_real_distribution<double> dist(1, std::nextafter(10, DBL_MAX));

3
Поскольку вопрос состоит в том, чтобы задать наиболее общий способ генерирования случайных чисел, который вы могли бы просто использовать default_random_engine, согласно
праймеру

2
@aaronman: Я пойду на выступление STL, где ему явно не нравится то, что default_random_engineсуществует.
Билл Линч

5
@ Крис, мы все знаем разницу между вектором и картой, не все знают разницу между mt19937 и ranlux24, если кто-то умудрился стать программистом, не зная, что такое вектор и словарь, возможно, они должны иметь std::default_container, надеюсь, что нет Люди, считающие себя программистами, которые не знают различий, многие языки сценариев имеют структуру типов карт по умолчанию, которая может быть реализована различными способами, которые пользователь может не знать
aaronman

21
nextafterВызов является излишеством для большинства приложений. Шансы на случайную doubleпосадку точно в конечной точке настолько малы, что нет практической разницы между включением и исключением.
Марк Рэнсом

3
@chris Не имеет отношения (но вы открыли дверь), ваша std::vectorаналогия здесь не работает, потому что std::vector на самом деле является хорошим значением по умолчанию из-за кэширования процессора. Это даже превосходит std::listдля вставки в середине. Это верно, даже если вы понимаете все контейнеры и можете принять обоснованное решение на основе алгоритмической сложности.
void.pointer

24

Моя «случайная» библиотека обеспечивает удобную оболочку вокруг случайных классов C ++ 11. Вы можете сделать почти все с помощью простого метода get.

Примеры:

  1. Случайное число в диапазоне

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
  2. Случайный логический

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
  3. Случайное значение из списка std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
  4. Случайный итератор из диапазона итератора или всего контейнера

    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


4

Я переделал все вышеперечисленное, около 40 других страниц с c ++ в нем, вот так, и посмотрел видео от Stephan T. Lavavej «STL», и все еще не был уверен, как случайные числа работают в практике, поэтому я взял целое воскресенье, чтобы выяснить, что это такое и как оно работает и может быть использовано.

По моему мнению, STL прав насчет «не использовать srand больше», и он хорошо объяснил это в видео 2 . Он также рекомендует использовать:

а) void random_device_uniform()- для зашифрованной генерации, но медленнее (из моего примера)

б) примеры с 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> and <cstdlib>#includes - так что будьте осторожны, изучая только одну книгу :).

Значение - это не должно использоваться с c ++ 11, потому что:

Программы часто нуждаются в источнике случайных чисел. До нового стандарта и C, и C ++ полагались на простую библиотечную функцию C с именем rand. Эта функция создает псевдослучайные целые числа, которые равномерно распределены в диапазоне от 0 до системно-зависимого максимального значения, которое составляет не менее 32767. Функция rand имеет несколько проблем: многим, если не большинству программ, нужны случайные числа в другом диапазоне от один произведенный рандом. Некоторые приложения требуют случайных чисел с плавающей точкой. Некоторым программам нужны числа, отражающие неравномерное распределение. Программисты часто вводят неслучайность, когда пытаются преобразовать диапазон, тип или распределение чисел, сгенерированных rand. (цитата из учебника Lippmans C ++, пятое издание 2012 г.)


В конце концов, я нашел лучшее объяснение из 20 книг Бьярна Страуструпса, более новых - и он должен знать свои вещи - в «Путешествии по C ++ 2019», «Принципы и практика программирования с использованием C ++ 2016» и «Язык программирования C ++, 4-е издание». 2014 », а также некоторые примеры из« Учебника по Липпману C ++, пятое издание 2012 »:

И это действительно просто, потому что генератор случайных чисел состоит из двух частей: (1) механизм, который генерирует последовательность случайных или псевдослучайных значений. (2) распределение, которое отображает эти значения в математическое распределение в диапазоне.


Несмотря на мнение парня из Microsoft STL, Бьярн Страуструпс пишет:

В стандартной библиотеке предусмотрены механизмы и распределения случайных чисел (§ 24.7). По умолчанию используется default_random_engine, который выбран для широкого применения и низкой стоимости.

void die_roll()Пример от Бьярна 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

Вот то, что я только что написал в том же духе:

#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;
}

~


0

Вот некоторый ресурс, который вы можете прочитать о генераторе псевдослучайных чисел.

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

В основном, случайные числа в компьютере требуют начального числа (это число может быть текущим системным временем).

замещать

std::default_random_engine generator;

По

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

-3

У вас есть две общие ситуации. Во-первых, вам нужны случайные числа, и вы не слишком беспокоитесь о качестве или скорости выполнения. В этом случае используйте следующий макрос

#define uniform() (rand()/(RAND_MAX + 1.0))

это дает вам p в диапазоне от 0 до 1 - эпсилон (если только RAND_MAX не превосходит точность двойного, но не беспокойтесь об этом, когда придете к нему).

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

Теперь дает случайное целое число от 0 до N -1.

Если вам нужны другие дистрибутивы, вы должны трансформировать p. Или иногда проще вызыватьiform () несколько раз.

Если вы хотите повторить поведение, начните с константы, в противном случае начните с вызова time ().

Теперь, если вы беспокоитесь о качестве или производительности во время выполнения, перепишите формуiform (). Но в противном случае не трогайте код. Всегда держите форму () от 0 до 1 минус эпсилон. Теперь вы можете обернуть библиотеку случайных чисел в C ++, чтобы создать более удобную форму (), но это своего рода опция среднего уровня. Если вас беспокоят характеристики ГСЧ, то также стоит потратить немного времени, чтобы понять, как работают базовые методы, а затем представить один из них. Таким образом, у вас есть полный контроль над кодом, и вы можете гарантировать, что с одним и тем же начальным числом последовательность всегда будет одинаковой, независимо от платформы или версии C ++, на которую вы ссылаетесь.


3
За исключением того, что не является равномерным (от 0 до N-1). Причина проста, давайте предположим, что N = 100 и RAND_MAX = 32758. Нет способа равномерно отобразить 32758 элементов (RAND_MAX) на 100 входов. Уникальным способом является ограничение 32000 и повторное выполнение rand (), если он выходит за пределы
amchacon

1
Если N равно 100, тогда ваш ГСЧ должен быть очень хорошим, чтобы можно было обнаружить отклонение от плоского распределения.
Малкольм Маклин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.