Generowanie losowej liczby całkowitej z zakresu

Potrzebuję funkcji, która wygenerowałaby losową liczbę całkowitą w danym zakresie (łącznie z wartościami obramowania). Nie znam wymagań jakościowych/losowych, mam cztery wymagania:

    Potrzebuję tego szybko. Mój projekt musi wygenerować miliony (a czasem nawet dziesiątki milionów) liczb losowych, a moja funkcja generatora prądu okazała się wąskim gardłem.
  • potrzebuję, żeby było w miarę jednorodne (użycie rand () jest w porządku).
  • zakresy min-max mogą być cokolwiek od do .
  • To musi być zalążek.

Obecnie mam następujący kod C++:

output = min + (rand() * (int)(max - min) / RAND_MAX)

Problem polega na tym, że nie jest ona tak naprawdę jednorodna - max jest zwracany tylko wtedy, gdy rand () = RAND_MAX (dla Visual C++ jest to 1/32727). Jest to poważny problem dla małych zakresów, takich jak , gdzie ostatnia wartość prawie nigdy nie jest zwracana.

Więc chwyciłem pióro i papier i wymyśliłem następujący wzór (który opiera się na(int) (n + 0.5) zaokrąglenie całkowite "trick": {]}

Tutaj wpisz opis obrazka

Ale nadal nie daje mi jednolitego podziału. Powtarzane biegi z 10000 próbek dają mi stosunek 37: 50: 13 dla wartości wartości -1, 0. 1.

Czy mógłbyś zasugerować lepszą formułę? (lub nawet cała pseudolosowa funkcja generatora liczb)
 128
Author: Matěj Zábský, 2011-02-15

12 answers

Szybkie, nieco lepsze od Twojego, ale wciąż nie odpowiednio jednolite rozwiązanie rozproszone jest

output = min + (rand() % static_cast<int>(max - min + 1))

Z wyjątkiem sytuacji, gdy wielkość przedziału jest potęgą 2, metoda ta wytwarza stronnicze niejednorodne rozproszone Liczby niezależnie od jakości rand(). Aby uzyskać kompleksowe badanie jakości tej metody, proszę przeczytać niniejszą .

 87
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-29 22:35:37

Najprostsza (a co za tym idzie najlepsza) odpowiedź C++ (przy użyciu standardu 2011) to

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);
Nie trzeba wymyślać koła od nowa. Nie musisz się martwić o stronniczość. Nie musisz się martwić o wykorzystanie czasu jako losowego ziarna.
 237
Author: Walter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-13 09:54:15

Jeśli twój kompilator obsługuje C++0x i korzystanie z niego jest opcją dla ciebie, to nowy standard <random> nagłówek prawdopodobnie spełni Twoje potrzeby. Ma wysoką jakość uniform_int_distribution, która akceptuje minimalne i maksymalne granice (włącznie z potrzebami), i możesz wybierać spośród różnych generatorów liczb losowych, aby podłączyć się do tej dystrybucji.

Oto kod, który generuje milion losowych int s równomiernie rozłożonych w [-57, 365]. Użyłem nowych urządzeń std <chrono> do czasu, jak wspomniałeś wydajność jest głównym problemem dla Ciebie.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Dla mnie (2,8 GHz Intel Core i5) to drukuje:

2.10268 e + 07 losowych liczb na sekundę.

Generator może zostać zalany poprzez przekazanie int do jego konstruktora:
    G g(seed);

Jeśli później okaże się, że int nie obejmuje zakresu potrzebnego dla twojej dystrybucji, można temu zaradzić zmieniając uniform_int_distribution w ten sposób (np. long long):

    typedef std::uniform_int_distribution<long long> D;

Jeśli później okaże się, że minstd_rand nie jest wystarczająco wysokiej jakości generator, który można również łatwo wymienić. Np.:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine
Posiadanie oddzielnej kontroli nad generatorem liczb losowych i rozkładem losowym może być dość wyzwalające.

Obliczyłem również (nie pokazałem) pierwsze 4 "momenty" tego rozkładu (używając minstd_rand) i porównałem je do wartości teoretycznych , próbując określić jakość rozkładu:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

(przedrostek x_ odnosi się do "oczekiwanego")

 59
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-26 21:14:34

Podzielmy problem na dwie części:

  • Wygeneruj losową liczbę n w zakresie od 0 do (max-min).
  • Dodaj min do tej liczby
Pierwsza część jest oczywiście najtrudniejsza. Załóżmy, że wartość zwracana przez rand() jest idealnie jednorodna. Użycie modulo doda bias do pierwszych (RAND_MAX + 1) % (max-min+1) liczb. Więc gdybyśmy mogli magicznie zmienić RAND_MAX na RAND_MAX - (RAND_MAX + 1) % (max-min+1), nie byłoby już żadnych uprzedzeń.

Okazuje się, że możemy użyć tej intuicji, jeśli jesteśmy skłonni dopuścić pseudo-nondeterminizm do czasu działania naszego algorytmu. Ilekroć rand () zwraca zbyt dużą liczbę, po prostu pytamy o inną liczbę losową, dopóki nie otrzymamy takiej, która jest wystarczająco mała.

Czas pracy jest teraz geometrycznie rozłożony, z wartością oczekiwaną 1/p Gdzie p jest prawdopodobieństwem uzyskania wystarczająco małej liczby przy pierwszej próbie. Ponieważ {[3] } jest zawsze mniejsza niż (RAND_MAX + 1) / 2, wiemy, że p > 1/2, więc spodziewana liczba iteracji będzie zawsze być mniej niż dwa dla dowolnego zakresu. Powinno być możliwe wygenerowanie dziesiątek milionów liczb losowych w czasie krótszym niż sekundę na standardowym procesorze za pomocą tej techniki.

EDIT:

Chociaż powyższe jest technicznie poprawne, odpowiedź DSimon jest prawdopodobnie bardziej przydatna w praktyce. Nie powinieneś tego robić sam. Widziałem wiele implementacji odrzucania próbkowania i często bardzo trudno jest sprawdzić, czy jest poprawny, czy nie.

 15
Author: Jørgen Fogh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-08 11:07:15

A może Twister Mersenne? Wdrożenie boost jest dość łatwe w użyciu i jest dobrze Przetestowane w wielu rzeczywistych aplikacjach. Sam wykorzystałem go w kilku projektach naukowych, takich jak sztuczna inteligencja i algorytmy ewolucyjne.

Oto ich przykład, w którym tworzą prostą funkcję do obracania sześciostronnej matrycy:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}
A tu jeszcze trochę stręczycielstwa tego generatora na wypadek, gdybyś nie był przekonany, że powinieneś go używać nad znacznie gorszymi rand():
Mersenne Twister jest " przypadkowym number " generator wymyślony przez Makoto Matsumoto i Takuji Nishimura; ich strona zawiera liczne implementacji algorytmu. Zasadniczo, Mersenne Twister jest bardzo duże przesunięcie liniowo-zwrotne Zarejestruj się. Algorytm działa na 19937 ziaren bitowych, przechowywanych w 624-elementowa tablica 32-bitowych niepodpisanych liczby całkowite. Wartość 2^19937-1 to Mersenne prime; technika dla manipulowanie materiał siewny jest oparty na starszy algorytm "skręcania" - stąd nazwa "Mersenne Twister".

Atrakcyjny aspekt Mersenne Twister to wykorzystanie binarnych operacji - w przeciwieństwie do czasochłonne mnożenie - dla generowanie liczb. Algorytm również ma bardzo długi okres i dobrze ziarnistość. Jest szybki i skuteczne w zastosowaniach innych niż kryptograficzne.

 13
Author: Aphex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 20:58:55
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Jest to odwzorowanie 32768 liczb całkowitych na (nMax-nMin+1) liczb całkowitych. Mapowanie będzie całkiem dobre, jeśli (nMax-nMin+1) jest mały (jak w wymaganiach). Należy jednak pamiętać, że jeśli (nMax-nMin+1) jest duży, mapowanie nie będzie działać (na przykład - nie można mapować wartości 32768 do wartości 30000 z równym prawdopodobieństwem). Jeśli takie zakresy są potrzebne - powinieneś użyć 32-bitowego lub 64-bitowego źródła losowego, zamiast 15-bitowej metody rand(), lub zignorować wyniki rand (), które są poza zakresem.

 11
Author: Lior Kogan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 21:24:37

Oto bezstronna wersja, która generuje liczby w [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Jeśli zakres jest stosunkowo mały, nie ma powodu, aby buforować prawą stronę porównania w pętli do.

 4
Author: Jeremiah Willcock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 20:23:42

Polecam Boost.Losowa biblioteka , jest bardzo szczegółowa i dobrze udokumentowana, pozwala jednoznacznie określić, jakiej dystrybucji chcesz, a w scenariuszach nie-kryptograficznych może faktycznie przewyższyć typową implementację rand w Bibliotece C.

 3
Author: DSimon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-15 20:15:14

Załóżmy, że min i max są wartościami int, [i] oznacza włączenie tej wartości, (i ) oznacza, że nie zawiera tej wartości, użycie powyższego, aby uzyskać właściwą wartość za pomocą C++ rand ()

Odniesienie: for () [] define, visit:

Https://en.wikipedia.org/wiki/Interval_ (matematyka)

Dla funkcji rand i srand lub zdefiniuj RAND_MAX, odwiedź stronę:

Http://en.cppreference.com/w/cpp/numeric/random/rand

[min, max]

int randNum = rand() % (max - min + 1) + min

(min, max]

int randNum = rand() % (max - min) + min + 1

[min, max)

int randNum = rand() % (max - min) + min

(min, max)

int randNum = rand() % (max - min - 1) + min + 1
 1
Author: Huang Kun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-01 07:37:21

W tym wątku omówiono już próbkowanie odrzucenia, ale chciałem zaproponować jedną optymalizację opartą na fakcie, że rand() % 2^something nie wprowadza żadnych stronniczości, jak już wspomniano powyżej.

Algorytm jest naprawdę prosty:

  • Oblicz najmniejszą moc 2 większą od długości przedziału
  • randomizuj jedną liczbę w tym" nowym " przedziale
  • Zwróć tę liczbę, jeśli jest mniejsza niż długość oryginalnego interwału
    • Odrzuć inaczej
Oto mój przykładowy kod:
int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

To działa dobrze zwłaszcza dla małych interwałów, ponieważ moc 2 będzie "bliżej" do rzeczywistej długości interwału, a więc liczba chybień będzie mniejsza.

PS
Oczywiście unikanie rekurencji byłoby bardziej efektywne (nie trzeba obliczać ponad i ponad pułapem dziennika..) ale myślałem, że jest bardziej czytelny dla tego przykładu.

 0
Author: Pado,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-21 15:37:46

Wzór na to jest bardzo prosty, więc spróbuj tego wyrażenia,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
 -1
Author: Sohail xIN3N,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-22 09:03:08

Poniższe wyrażenie powinno być bezstronne, jeśli się nie mylę:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Zakładam tutaj, że rand() daje losową wartość w zakresie od 0,0 do 1,0, nie wliczając 1,0 i że max i min są liczbami całkowitymi z warunkiem, że min

 -2
Author: Moritz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-20 03:53:52