Генеруйте випадкові числа за допомогою випадкової бібліотеки 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як двигун, якщо у вас немає вагомих причин цього не робити. А розподіл - це замкнутий інтервал на обох кінцях.
chris


2
@chris розповсюдження не закрито на обох кінцях, перевірте це посилання або посилання
memo1288

1
@ Memo1288, спасибі, я думав , що ОП було використання std::uniform_int_distribution, який буде закритий на обох кінцях.
chris

Відповіді:


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, згідно з c ++ праймером саме ця реалізація вважається найкориснішою
aaronman

2
@aaronman: Я проходжу розмову STL, де йому явно не подобається, що default_random_engineіснує.
Білл Лінч

5
@chris всі ми знаємо різницю між вектором та картою, не всі знають різницю між mt19937 та ranlux24, якщо комусь вдалося стати програмістом, не знаючи, що вектор і словник, можливо, вони повинні мати std::default_container, сподіваємось, їх немає люди, які вважають себе програмістами, які не знають відмінностей, багато мов сценаріїв мають структуру типу карт за замовчуванням, яка може бути реалізована різними способами, які користувач може не знати
aaronman

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

3
@ Кріс Unrelated (але ви відкрили двері), ваша std::vectorаналогія тут не працює , тому що std::vector це на самому справі хороший по замовчуванням з - за процесора кешування. Він навіть перевершує std::listвставку посередині. Це вірно, навіть якщо ви розумієте всі контейнери і зможете прийняти обгрунтоване рішення на основі алгоритмічної складності.
void.pointer

24

Моя "випадкова" бібліотека забезпечує високу зручну обгортку навколо C ++ 11 випадкових класів. Ви можете зробити майже всі речі простим методом "отримати".

Приклади:

  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 ++, як це, і переглядав відео Стефана Т. Лававея "STL" і досі не був впевнений, як випадкові числа працюють у практиці, тому я взяв повну неділю, щоб зрозуміти про що це все і як це працює і чи можна використовувати.

На мій погляд, STL має рацію щодо того, що "більше не використовуйте сранд", і він це добре пояснив у відео 2 . Він також рекомендує використовувати:

а) void random_device_uniform()- для зашифрованого генерування, але повільніше (з мого прикладу)

б) приклади з mt19937- швидше, здатність створювати насіння, не зашифровані


Я витягнув усі заявлені на с ++ 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> включень - тому будьте обережні, щоб дізнатися лише з однієї книги :).

Значення - його не слід використовувати з c ++ 11, оскільки:

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


Нарешті, я знайшов найкраще пояснення з 20 книг у «Біярн Структурпс» новіших - і він повинен знати свої речі - у «Подорожі C ++ 2019», «Принципи програмування та практики використання C ++ 2016» та «Мова програмування C ++ 4-е видання 2014 ", а також кілька прикладів у" Lippmans C ++ грунтовці п'ятого видання 2012 ":

І це дуже просто, тому що генератор випадкових чисел складається з двох частин: (1) двигун, який виробляє послідовність випадкових чи псевдовипадкових значень. (2) розподіл, який відображає ці значення в математичний розподіл у діапазоні.


Незважаючи на думку хлопця Microsofts STL, Bjarne Stroustrups пише:

У стандартній бібліотеці передбачені двигуни та розподіли випадкових чисел (§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;
    }

Я думаю, що це все додає, і, як я вже сказав, мені знадобилося чимало читання та час, щоб перерахувати це на приклади - якщо у вас є додаткові відомості про генерацію чисел, я радий дізнатися про це через вечір або в розділі коментарів і додасть її за потреби або відредагує цю публікацію. Бул


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) (рівномірний () * N);

Тепер дає випадкове ціле число на 0 до N -1.

Якщо вам потрібні інші дистрибутиви, вам доведеться перетворити p. Або іноді простіше викликати уніформу () кілька разів.

Якщо ви хочете повторюваної поведінки, насіньте з постійною, інакше насінням із закликом до часу ().

Тепер, якщо вас турбує якість або продуктивність часу, перепишіть форму (). Але в іншому випадку не торкайтеся коду. Завжди тримайте рівномірний () на 0 до 1 мінус епсилон. Тепер ви можете обернути бібліотеку випадкових чисел C ++, щоб створити кращу форму (), але це такий варіант середнього рівня. Якщо ви турбуєтесь про характеристики RNG, тоді також варто вкласти трохи часу, щоб зрозуміти, як працюють основні методи, тоді надайте їх. Таким чином, ви маєте повний контроль над кодом, і ви можете гарантувати, що з тим самим насінням послідовність завжди буде точно однаковою, незалежно від платформи або до якої версії C ++ ви посилаєтесь.


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

1
Якщо N дорівнює 100, то ваш RNG повинен бути надзвичайно хорошим, щоб мати можливість виявити відхилення від плоского розподілу.
Малькольм Маклін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.