Generuj liczby losowe za pomocą biblioteki losowej C ++ 11


135

Jak sugeruje tytuł, próbuję wymyślić sposób generowania liczb losowych za pomocą nowej <random>biblioteki C ++ 11 . Wypróbowałem to z tym kodem:

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

Problem z kodem, który mam, polega na tym, że za każdym razem, gdy go kompiluję i uruchamiam, generuje zawsze te same liczby. Więc moje pytanie brzmi: jakie inne funkcje w bibliotece losowej mogą to osiągnąć, będąc naprawdę losowymi?

W moim konkretnym przypadku próbowałem uzyskać wartość z zakresu [1, 10]


3
To pytanie niebezpiecznie graniczy z „głównie opiniotwórczym”. Jeśli możesz pozbyć się prośby o opinię, to widzę, że to pytanie jest bardzo przydatne (jeśli nie zostało jeszcze zadane).
John Dibling

4
Proponuję użyć std::mt19937jako silnika, chyba że masz dobry powód, aby tego nie robić. Dystrybucja to zamknięty przedział na obu końcach.
chris


2
@chris dystrybucja nie jest zamknięta z obu stron, sprawdź ten link lub ten link
memo1288

1
@ memo1288, Dziękuję, myślałem, że OP używa a std::uniform_int_distribution, który jest zamknięty z obu stron.
chris

Odpowiedzi:


191

Stephan T. Lavavej (stl) z Microsoftu wygłosił wykład na Going Native o tym, jak używać nowych losowych funkcji C ++ 11 i dlaczego nie używać rand(). Umieścił w nim slajd, który zasadniczo rozwiązuje twoje pytanie. Skopiowałem kod z tego slajdu poniżej.

Pełne jego wystąpienie można zobaczyć tutaj: 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";
}

Używamy random_deviceraz do zaszczepienia nazwanego generatora liczb losowych mt. random_device()jest wolniejszy niż mt19937, ale nie musi być inicjowany, ponieważ żąda losowych danych z systemu operacyjnego (które będą pobierane z różnych lokalizacji, takich jak na przykład RdRand ).


Patrząc na to pytanie / odpowiedź , wydaje się, że uniform_real_distributionzwraca liczbę z zakresu [a, b), w którym chcesz [a, b]. Aby to zrobić, nasz uniform_real_distibutionpowinien wyglądać tak:

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

3
Ponieważ pytanie dotyczy najbardziej ogólnego sposobu generowania liczb losowych, którego możesz chcieć po prostu użyć default_random_engine, zgodnie z podkładem c ++ jest to ten, który implementacja uznała za najbardziej użyteczną
aaronman

2
@aaronman: Idę przez rozmowę STL, gdzie wyraźnie mu się to nie podoba default_random_engine.
Bill Lynch,

5
@chris wszyscy znamy różnicę między wektorem a mapą, nie każdy zna różnicę między mt19937 a ranlux24, jeśli komuś udało się zostać programistą nie wiedząc, czym jest wektor i słownik, to może powinien mieć std::default_container, mam nadzieję, że nie ma ludzie uważający się za programistów, którzy nie znają różnic, wiele języków skryptowych ma domyślną strukturę typu mapy, którą można zaimplementować na wiele różnych sposobów, których użytkownik może nie znać
aaronman

21
W nextafterprzypadku większości aplikacji wezwanie to przesada. Szanse na losowe doublelądowanie dokładnie na punkcie końcowym są tak małe, że nie ma praktycznej różnicy między włączeniem a wykluczeniem.
Mark Ransom

3
@chris Niepowiązane (ale otworzyłeś drzwi), twoja std::vectoranalogia nie działa tutaj, ponieważ std::vector jest to w rzeczywistości dobra wartość domyślna z powodu buforowania procesora. Nawet przewyższa je std::listwkładaniem pośrodku. To prawda, nawet jeśli rozumiesz wszystkie kontenery i możesz podjąć świadomą decyzję na podstawie złożoności algorytmicznej.
void.pointer

24

Moja „losowa” biblioteka zapewnia wygodne i wygodne opakowanie wokół losowych klas w C ++ 11. Możesz zrobić prawie wszystko za pomocą prostej metody „pobierz”.

Przykłady:

  1. Losowa liczba w zakresie

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
  2. Losowe wartości logiczne

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
  3. Losowa wartość z listy std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
  4. Losowy iterator z zakresu iteratora lub całego kontenera

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

I jeszcze więcej rzeczy! Sprawdź stronę github:

https://github.com/effolkronium/random


4

Podpisałem wszystkie powyższe rzeczy, około 40 innych stron z c ++ w ten sposób i obejrzałem wideo Stephana T. Lavaveja "STL" i nadal nie byłem pewien, jak liczby losowe działają w praktyce, więc poświęciłem całą niedzielę, aby się dowiedzieć o co w tym wszystkim chodzi i jak to działa i może być używane.

Moim zdaniem STL ma rację co do „nieużywania już srand” i wyjaśnił to dobrze w wideo 2 . Zaleca również użycie:

a) void random_device_uniform()- dla zaszyfrowanego generowania, ale wolniej (z mojego przykładu)

b) przykłady z mt19937- szybszym, możliwością tworzenia seedów, nie jest szyfrowane


Wyciągnąłem wszystkie zgłoszone książki C ++ 11, do których mam dostęp, i stwierdziłem, że niemieccy autorzy, tacy jak Breymann (2015), nadal używają klonu

srand( time( 0 ) );
srand( static_cast<unsigned int>(time(nullptr))); or
srand( static_cast<unsigned int>(time(NULL))); or

tylko z <random>zamiast <time> and <cstdlib>#includings - więc uważaj, aby uczyć się tylko z jednej książki :).

Znaczenie - tego nie należy używać od czasu C ++ 11, ponieważ:

Programy często potrzebują źródła liczb losowych. Przed nowym standardem zarówno C, jak i C ++ korzystały z prostej funkcji biblioteki C o nazwie rand. Ta funkcja tworzy pseudolosowe liczby całkowite, które są równomiernie rozłożone w zakresie od 0 do zależnej od systemu wartości maksymalnej wynoszącej co najmniej 32767. Funkcja rand ma kilka problemów: Wiele programów, jeśli nie większość, potrzebuje liczb losowych z innego zakresu niż jeden wyprodukowany przez rand. Niektóre aplikacje wymagają losowych liczb zmiennoprzecinkowych. Niektóre programy wymagają liczb, które odzwierciedlają niejednorodną dystrybucję. Programiści często wprowadzają nielosowość, gdy próbują przekształcić zakres, typ lub rozkład liczb generowanych przez rand. (cytat z Lippmans C ++ primer piąta edycja 2012)


W końcu znalazłem najlepsze wyjaśnienie z 20 nowszych książek Bjarne Stroustrupsa - a on powinien znać się na swoich rzeczach - w „Wycieczka po C ++ 2019”, „Zasady programowania i praktyka przy użyciu C ++ 2016” oraz „Język programowania C ++, wydanie 4 2014 ”, a także kilka przykładów w„ Lippmans C ++ primer piąta edycja 2012 ”:

Jest to naprawdę proste, ponieważ generator liczb losowych składa się z dwóch części: (1) silnika, który tworzy sekwencję wartości losowych lub pseudolosowych. (2) rozkład, który odwzorowuje te wartości w matematycznym rozkładzie w zakresie.


Wbrew opinii gościa z Microsoftu STL, Bjarne Stroustrups pisze:

W programie biblioteka standardowa zapewnia silniki liczb losowych i dystrybucje (§24.7). Domyślnie użyj default_random_engine, który jest wybrany ze względu na szerokie zastosowanie i niski koszt.

void die_roll()Przykład pochodzi z Bjarne Stroustrups - dobry pomysł silnik generowania i dystrybucji z using (bardziej że walka tutaj) .


Aby móc w praktyce skorzystać z generatorów liczb losowych dostarczonych przez bibliotekę standardową, <random> tutaj trochę kodu wykonywalnego z różnymi przykładami zredukowanymi do najmniej potrzebnego, który, miejmy nadzieję, pozwoli wam zaoszczędzić czas i pieniądze:

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

Myślę, że to wszystko sumuje i tak jak powiedziałem, zajęło mi sporo czytania i czasu, aby odłożyć to do tych przykładów - jeśli masz więcej rzeczy na temat generowania liczb, cieszę się, że o tym usłyszę po południu lub w sekcji komentarzy i doda go w razie potrzeby lub edytuje ten post. Bool


0

Oto coś, co właśnie napisałem w tych liniach:

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

~



-3

Masz dwie typowe sytuacje. Po pierwsze, chcesz liczb losowych i nie przejmujesz się jakością ani szybkością wykonywania. W takim przypadku użyj następującego makra

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

to daje p w zakresie od 0 do 1 - epsilon (chyba że RAND_MAX jest większe niż dokładność podwójnej, ale martw się o to, kiedy do tego dojdziesz).

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

Teraz daje losową liczbę całkowitą od 0 do N -1.

Jeśli potrzebujesz innych dystrybucji, musisz przekształcić p. Czasami łatwiej jest kilka razy wywołać uniform ().

Jeśli chcesz, aby zachowanie było powtarzalne, zaszczepia stałą, w przeciwnym razie ziarno wywołuje time ().

Teraz, jeśli martwisz się jakością lub wydajnością w czasie wykonywania, przepisz uniform (). Ale poza tym nie dotykaj kodu. Zawsze trzymaj uniform () na 0 do 1 minus epsilon. Teraz możesz zawinąć bibliotekę liczb losowych C ++, aby utworzyć lepszy uniform (), ale jest to rodzaj opcji średniego poziomu. Jeśli przejmujesz się charakterystyką RNG, warto również poświęcić trochę czasu, aby zrozumieć, jak działają podstawowe metody, a następnie podać jedną. Masz więc pełną kontrolę nad kodem i możesz zagwarantować, że przy tym samym ziarnie sekwencja będzie zawsze dokładnie taka sama, niezależnie od platformy lub wersji C ++, z którą się łączysz.


3
Tyle że to nie jest jednolite (od 0 do N-1). Powód jest prosty, załóżmy, że N = 100 i RAND_MAX = 32758. Nie ma sposobu na jednolite odwzorowanie 32758 elementów (RAND_MAX) na 100 wejść. Unikalnym sposobem jest ustawienie ograniczenia na 32000 i ponowne wykonanie rand (), jeśli wyjdzie poza granice
amchacon

1
Jeśli N wynosi 100, wówczas RNG musi być bardzo dobre, aby móc wykryć odchylenie od rozkładu płaskiego.
Malcolm McLean
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.