Generowanie losowej liczby całkowitej z zakresu

157

Potrzebuję funkcji, która wygeneruje losową liczbę całkowitą w podanym zakresie (w tym wartości graniczne). Nie mam nieuzasadnionych wymagań dotyczących jakości / losowości, mam cztery wymagania:

  • Potrzebuję tego, żeby był szybki. Mój projekt musi generować miliony (a czasem nawet dziesiątki milionów) liczb losowych, a moja obecna funkcja generatora okazała się wąskim gardłem.
  • Potrzebuję, aby był w miarę jednolity (użycie rand () jest całkowicie w porządku).
  • zakresy min-max mogą wynosić od <0, 1> do <-32727, 32727>.
  • musi być zaszczepiany.

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

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

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

Więc złapałem długopis i papier i wymyśliłem następującą formułę (która opiera się na sztuczce zaokrąglania liczb całkowitych (int) (n + 0,5)):

wprowadź opis obrazu tutaj

Ale to nadal nie daje mi jednolitej dystrybucji. Powtarzane serie z 10000 próbek dają mi stosunek 37:50:13 dla wartości -1, 0,1.

Czy mógłbyś zaproponować lepszą formułę? (lub nawet cała funkcja generatora liczb pseudolosowych)

Matěj Zábský
źródło
3
@Bill MaGriff: tak. Ma ten sam problem. Uproszczona wersja brzmi: jak podzielić równo 10 cukierków pomiędzy troje dzieci (bez rozbijania żadnego z cukierków)? Odpowiedź brzmi: nie możesz - musisz każdemu dziecku dać po trzy, a dziesiątego nikomu nie dać.
Jerry Coffin
5
Czy spojrzałeś na Boost.Random ?
Fred Nurk,
3
Przeczytaj artykuł Andrew Koeniga „Prosty problem, który prawie nigdy nie jest poprawnie rozwiązany”: drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev,
1
@Gene Bushuyev: Zarówno Andrew, jak i ja rozpamiętywaliśmy ten temat od dłuższego czasu. Zobacz: groups.google.com/group/comp.lang.c++/browse_frm/thread/… i: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Odpowiedzi:

105

Jest to szybkie, nieco lepsze niż twoje, ale wciąż niejednorodne rozwiązanie rozproszone

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

Z wyjątkiem sytuacji, gdy rozmiar zakresu jest potęgą 2, ta metoda daje tendencyjne niejednorodne rozłożone liczby niezależnie od jakości rand(). Aby uzyskać kompleksowy test jakości tej metody, przeczytaj to .

Mark B.
źródło
2
Dzięki, po szybkich testach wydaje mi się to wystarczająco dobre - jego dystrybucja dla -1, 0, 1 to prawie 33:33:33.
Matěj Zábský
3
Zawsze zwraca wartość maksymalną. Czy coś mi tu brakuje? : |
rohan-patel,
15
rand()należy uznać za szkodliwe w C ++ , są znacznie lepsze sposoby na uzyskanie czegoś, co jest równomiernie rozmieszczone i faktycznie losowe.
Mgetz
1
Czy naprawdę zwraca poprawną liczbę w zakresie 100% czasu? Znalazłem tutaj inną odpowiedź typu stackoverflow, która używa rekurencji, aby zrobić to "we właściwy sposób": stackoverflow.com/a/6852396/623622
Czarek Tomczak
2
Ponieważ jest to bardzo pozytywna (niż pożądana) odpowiedź, która wydaje się wiarygodnym źródłem informacji dla wielu nowych czytelników, myślę, że bardzo ważne jest, aby wspomnieć o jakości i potencjalnych zagrożeniach związanych z tym rozwiązaniem, dlatego dokonałem edycji.
plasmacel
296

Najprostszą (a przez to najlepszą) odpowiedzią w C ++ (przy użyciu standardu 2011) jest

#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 ma potrzeby ponownego wynajdywania koła. Nie musisz się martwić o uprzedzenia. Nie musisz martwić się o wykorzystanie czasu jako losowego ziarna.

Walter
źródło
1
W dzisiejszych czasach to powinna być odpowiedź . Więcej funkcji zawiera odniesienie do generowania liczb pseudolosowych .
alekstoind
8
Zgadzam się na „najprostsze” (i najbardziej idiomatyczne), a nie na „najlepsze”. Niestety standard nie daje żadnej gwarancji random_device, która w niektórych przypadkach może zostać całkowicie złamana . Ponadto, mt19937chociaż jest to bardzo dobry wybór do zastosowań ogólnych, nie jest najszybszym z generatorów dobrej jakości (patrz to porównanie ), a zatem może nie być idealnym kandydatem do PO.
Alberto M
1
@AlbertoM Niestety, porównanie, do którego się odwołujesz, nie dostarcza wystarczającej ilości szczegółów i nie jest odtwarzalne, co budzi wątpliwości (zresztą pochodzi z 2015 roku, a moja odpowiedź pochodzi z 2013 roku). Może to prawda, że ​​istnieją lepsze metody (i miejmy nadzieję, że w przyszłości minstdbędzie taka metoda), ale to postęp. Co do słabej implementacji random_device- to okropne i powinno być uznane za błąd (prawdopodobnie także standardu C ++, jeśli na to pozwala).
Walter
1
Całkowicie się z tobą zgadzam; I faktycznie nie chcą krytykować swoje rozwiązanie per se , tylko chciał ostrzec przypadkowego czytelnika, że ostateczna odpowiedź w tej sprawie, mimo obietnic c ++ 11, jest jeszcze napisane. Zamierzam zamieścić omówienie tematu od 2015 roku jako odpowiedź na powiązane pytanie .
Alberto M
1
To jest „najprostsze”? Czy mógłbyś wyjaśnić, dlaczego zdecydowanie prostsza rand()opcja nie jest opcją i czy ma to znaczenie w przypadku niekrytycznego zastosowania, takiego jak generowanie losowego indeksu pivot? Czy muszę się też martwić konstruowaniem random_device/ mt19937/ uniform_int_distributionw ścisłej pętli / funkcji wbudowanej? Czy wolę raczej je przekazywać?
bluenote10
60

Jeśli Twój kompilator obsługuje C ++ 0x i używanie go jest opcją dla Ciebie, to nowy standardowy <random>nagłówek prawdopodobnie spełni Twoje potrzeby. Ma wysoką jakość, uniform_int_distributionktó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 liczb intrównomiernie rozmieszczonych w [-57, 365]. Użyłem nowych funkcji standardowego <chrono>czasu, ponieważ wspomniałeś, że wydajność jest dla ciebie głównym problemem.

#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 wypisuje:

2.10268e + 07 liczb losowych na sekundę.

Możesz zaszczepić generator, przekazując int do jego konstruktora:

    G g(seed);

Jeśli później okaże się, że intnie obejmuje zakresu potrzebnego do dystrybucji, można temu zaradzić, zmieniając coś uniform_int_distributionpodobnego (np. Na long long):

    typedef std::uniform_int_distribution<long long> D;

Jeśli później okaże się, że minstd_randgenerator nie jest wystarczająco wysokiej jakości, można go łatwo wymienić. Na przykład:

    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 pokazano) pierwsze 4 „momenty” tego rozkładu (używając minstd_rand) i porównałem je z wartościami teoretycznymi, próbując określić ilościowo 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

( x_Przedrostek oznacza „oczekiwany”)

Howard Hinnant
źródło
3
W tej odpowiedzi można użyć krótkiego podsumowującego fragmentu kodu, który pokazuje tylko kod, który jest faktycznie potrzebny do wygenerowania losowej liczby całkowitej z zakresu.
arekolek
Problem jest ułatwiony przez fakt, że min i max rozkładu nigdy się nie zmieniają. A co by było, gdybyś musiał tworzyć dw każdej iteracji z różnymi ograniczeniami? Jak bardzo spowolniłoby to pętlę?
quant_dev
15

Podzielmy problem na dwie części:

  • Wygeneruj liczbę losową nz zakresu od 0 do (maks-min).
  • Dodaj min do tej liczby

Pierwsza część jest oczywiście najtrudniejsza. Załóżmy, że wartość zwracana przez rand () jest idealnie jednolita. Użycie modulo doda odchylenie do pierwszych (RAND_MAX + 1) % (max-min+1)liczb. Więc gdybyśmy mogli magicznie zmienić się RAND_MAXna RAND_MAX - (RAND_MAX + 1) % (max-min+1), nie byłoby już żadnych uprzedzeń.

Okazuje się, że możemy skorzystać z tej intuicji, jeśli jesteśmy skłonni dopuścić pseudo-niedeterminizm do czasu działania naszego algorytmu. Za każdym razem, gdy rand () zwraca zbyt dużą liczbę, po prostu prosimy o inną liczbę losową, aż otrzymamy wystarczająco małą.

Czas działania jest teraz rozłożony geometrycznie , z wartością oczekiwaną, 1/pgdzie pjest prawdopodobieństwo uzyskania wystarczająco małej liczby przy pierwszej próbie. Ponieważ RAND_MAX - (RAND_MAX + 1) % (max-min+1)jest zawsze mniejsze niż (RAND_MAX + 1) / 2, wiemy o tym p > 1/2, więc oczekiwana liczba iteracji zawsze będzie mniejsza niż dwa dla dowolnego zakresu. Powinno być możliwe wygenerowanie dziesiątek milionów liczb losowych w mniej niż sekundę na standardowym procesorze z tą techniką.

EDYTOWAĆ:

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

Jørgen Fogh
źródło
Dla kompletności: to jest próba odrzucenia .
etarion
3
Ciekawostka: Joel Spolsky wspomniał kiedyś o wersji tego pytania jako przykładzie tego, na co StackOverflow dobrze odpowiadał. Przejrzałem odpowiedzi na odrzucenie miejscu pobierania próbek z udziałem w tym czasie, a każdy pojedynczy jeden był nieprawidłowy.
Jørgen Fogh
13

Co powiesz na Mersenne Twister ? Implementacja przyspieszenia jest raczej łatwa w użyciu i dobrze przetestowana w wielu rzeczywistych aplikacjach. Sam używałem go w kilku akademickich projektach, takich jak sztuczna inteligencja i algorytmy ewolucyjne.

Oto ich przykład, w którym wykonują prostą funkcję rzucania sześciościenną kostką:

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

Aha, a tutaj jeszcze trochę stręczycielstwa tego generatora, na wypadek gdybyś nie był przekonany, że powinieneś go używać na znacznie gorszym rand():

Mersenne Twister to generator „liczb losowych” wymyślony przez Makoto Matsumoto i Takuji Nishimurę; ich strona internetowa zawiera liczne implementacje algorytmu.

Zasadniczo Mersenne Twister to bardzo duży rejestr przesuwny z liniowym sprzężeniem zwrotnym. Algorytm działa na podstawie 19 937 bitów przechowywanych w 624-elementowej tablicy 32-bitowych liczb całkowitych bez znaku. Wartość 2 ^ 19937-1 jest liczbą pierwszą Mersenne'a; technika manipulowania ziarnem oparta jest na starszym algorytmie „skręcania” - stąd nazwa „Mersenne Twister”.

Atrakcyjnym aspektem Mersenne Twister jest wykorzystanie operacji binarnych - w przeciwieństwie do czasochłonnego mnożenia - do generowania liczb. Algorytm ma również bardzo długi okres i dobrą ziarnistość. Jest zarówno szybki, jak i skuteczny w aplikacjach niekryptograficznych.

Aphex
źródło
1
Twister Mersenne jest dobrym generatorem, ale problem, z którym ma do czynienia, pozostaje, niezależnie od samego generatora.
Jerry Coffin
Nie chcę używać Boost tylko dla generatora losowego, ponieważ (ponieważ mój projekt jest biblioteką) oznacza to wprowadzenie kolejnej zależności do projektu. Prawdopodobnie i tak będę zmuszony w przyszłości go używać, więc wtedy mogę przełączyć się na ten generator.
Matěj Zábský
1
@Jerry Coffin Jaki problem? Zaproponowałem to, ponieważ spełnia wszystkie jego wymagania: jest szybki, jest jednolity (przy użyciu boost::uniform_introzkładu), możesz przekształcić zakresy min max na cokolwiek chcesz i można go obsiać.
Aphex
@mzabsky Prawdopodobnie nie pozwoliłbym, żeby to mnie powstrzymało, kiedy musiałem wysłać moje projekty do moich profesorów w celu przesłania, po prostu dołączyłem odpowiednie pliki nagłówkowe, których używałem; nie powinieneś być zmuszony do pakowania całej 40 MB biblioteki boost ze swoim kodem. Oczywiście w twoim przypadku może to być niewykonalne z innych powodów, takich jak prawa autorskie ...
Aphex,
@Aphex Mój projekt nie jest symulatorem naukowym ani czymś, co wymaga naprawdę jednolitej dystrybucji. Używałem starego generatora przez 1,5 roku bez żadnego problemu, zauważyłem tendencyjną dystrybucję dopiero, gdy po raz pierwszy potrzebowałem go do generowania liczb z bardzo małego zakresu (w tym przypadku 3). Jednak prędkość jest nadal argumentem, aby rozważyć rozwiązanie doładowania. Przyjrzę się jego licencji, aby sprawdzić, czy mogę po prostu dodać kilka potrzebnych plików do mojego projektu - podoba mi się „Do kasy -> F5 -> gotowy do użycia”, tak jak jest teraz.
Matěj Zábský
11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

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

Lior Kogan
źródło
Pomimo jego niepopularności, używam tego również w moich projektach pozanaukowych. Łatwy do zrozumienia (nie potrzebujesz dyplomu z matematyki) i działa odpowiednio (nigdy nie musiałem profilować żadnego kodu za jego pomocą). :) W przypadku dużych zakresów myślę, że moglibyśmy połączyć razem dwie wartości rand () i uzyskać 30-bitową wartość do pracy (zakładając RAND_MAX = 0x7fff, czyli 15 losowych bitów)
efotinis
zmień RAND_MAXna, (double) RAND_MAXaby uniknąć ostrzeżenia o przepełnieniu całkowitoliczbowym.
Alex
4

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 twój zakres jest dość mały, nie ma powodu, aby buforować prawą stronę porównania w dopętli.

Jeremiah Willcock
źródło
IMO, żadne z przedstawionych tam rozwiązań nie jest tak naprawdę duże. Jego rozwiązanie oparte na pętli działa, ale prawdopodobnie będzie dość nieefektywne, zwłaszcza dla małego zakresu, takiego jak omawia OP. Jego jednolite rozwiązanie odchylenia w rzeczywistości wcale nie powoduje odchyleń munduru . Co najwyżej maskuje brak jednolitości.
Jerry Coffin
@Jerry: Sprawdź nową wersję.
Jeremiah Willcock
Nie jestem pewien, czy to działa poprawnie. Może, ale poprawność nie wydaje się oczywista, przynajmniej dla mnie.
Jerry Coffin
@Jerry: Oto moje rozumowanie: załóżmy, że zakres jest [0, h)dla uproszczenia. Wywołanie rand()ma RAND_MAX + 1możliwe wartości zwracane; przyjmowanie rand() % hzwinięć (RAND_MAX + 1) / hich do każdej z hwartości wyjściowych, z wyjątkiem tego, że (RAND_MAX + 1) / h + 1z nich są mapowane na wartości mniejsze niż (RAND_MAX + 1) % h(z powodu ostatniego częściowego cyklu przez hwyjścia). Dlatego usuwamy (RAND_MAX + 1) % hmożliwe wyniki, aby uzyskać bezstronny rozkład.
Jeremiah Willcock
3

Polecam bibliotekę Boost.Random , jest bardzo szczegółowa i dobrze udokumentowana, pozwala wyraźnie określić, jaką dystrybucję chcesz, aw scenariuszach niekryptograficznych może faktycznie przewyższać typową implementację rand biblioteki C.

DSimon
źródło
1

załóżmy, że min i max są wartościami int, [i] oznacza dołączenie tej wartości, (i) oznacza, że ​​nie należy uwzględniać tej wartości, używając powyższego do uzyskania właściwej wartości za pomocą c ++ rand ()

odniesienie: for () [] zdefiniuj, odwiedź:

https://en.wikipedia.org/wiki/Interval_(mathematics)

dla funkcji rand i srand lub zdefiniuj RAND_MAX odwiedź:

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

[minimum maksimum]

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

(minimum maksimum]

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

[minimum maksimum)

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

(minimum maksimum)

int randNum = rand() % (max - min - 1) + min + 1
Huang Kun
źródło
0

W tym wątku próbkowanie odrzucania było już omówione, ale chciałem zasugerować jedną optymalizację opartą na tym fakcie rand() % 2^something nie wprowadza żadnego błędu, jak już wspomniano powyżej.

Algorytm jest naprawdę prosty:

  • obliczyć najmniejszą potęgę 2 większą niż długość interwału
  • losuj jedną liczbę w tym „nowym” przedziale
  • zwraca tę liczbę, jeśli jest mniejsza niż długość pierwotnego interwału
    • odrzucić 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
} 

Działa to dobrze zwłaszcza w przypadku małych interwałów, ponieważ potęga 2 będzie „bliżej” rzeczywistej długości interwału, a więc liczba chybionych będzie mniejsza.

PS
Oczywiście unikanie rekurencji byłoby bardziej wydajne (nie ma potrzeby obliczania ponad pułapem dziennika ..), ale pomyślałem, że w tym przykładzie będzie bardziej czytelny.

Pado
źródło
0

Zauważ, że w większości sugestii początkowa losowa wartość, którą otrzymałeś z funkcji rand (), która zwykle wynosi od 0 do RAND_MAX, jest po prostu marnowana. Tworzysz z tego tylko jedną liczbę losową, podczas gdy istnieje rozsądna procedura, która może dać ci więcej.

Załóżmy, że chcesz [min, max] region liczb całkowitych losowych. Zaczynamy od [0, max-min]

Weź podstawę b = max-min + 1

Zacznij od przedstawienia liczby otrzymanej z rand () w bazie b.

W ten sposób masz piętro (log (b, RAND_MAX)), ponieważ każda cyfra w bazie b, z wyjątkiem ostatniej, reprezentuje losową liczbę z zakresu [0, max-min].

Oczywiście końcowe przesunięcie do [min, max] jest proste dla każdej liczby losowej r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Jeśli NUM_DIGIT jest liczbą cyfr w podstawie b, którą można wyodrębnić, to znaczy

NUM_DIGIT = floor(log(b,RAND_MAX))

to powyższe jest prostą implementacją wyodrębnienia NUM_DIGIT liczb losowych od 0 do b-1 z jednej liczby losowej RAND_MAX, zapewniając b <RAND_MAX.

alex.peter
źródło
-1

Wzór na to jest bardzo prosty, więc wypróbuj to wyrażenie,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Sohail xIN3N
źródło
2
Cały problem polegał na używaniu rand C / C ++, który zwraca liczbę całkowitą w zakresie określonym przez środowisko wykonawcze. Jak pokazano w tym wątku, mapowanie losowych liczb całkowitych od [0, RAND_MAX] do [MIN, MAX] nie jest całkowicie proste, jeśli chcesz uniknąć zniszczenia ich właściwości statystycznych lub wydajności. Jeśli masz dublety w zasięgu [0, 1], mapowanie jest łatwe.
Matěj Zábský
2
Twoja odpowiedź jest błędna, zamiast tego użyj modułu:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes
-2

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ść z zakresu od 0,0 do 1,0 NIE wliczając 1,0 i że max i min to liczby całkowite z warunkiem, że min <max.

Moritz
źródło
std::floorzwraca doublei potrzebujemy tutaj wartości całkowitej. Po prostu rzucałbym do intzamiast używać std::floor.
musiphil