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)):
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)
Odpowiedzi:
Jest to szybkie, nieco lepsze niż twoje, ale wciąż niejednorodne rozwiązanie rozproszone
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 .źródło
rand()
należy uznać za szkodliwe w C ++ , są znacznie lepsze sposoby na uzyskanie czegoś, co jest równomiernie rozmieszczone i faktycznie losowe.Najprostszą (a przez to najlepszą) odpowiedzią w C ++ (przy użyciu standardu 2011) jest
Nie ma potrzeby ponownego wynajdywania koła. Nie musisz się martwić o uprzedzenia. Nie musisz martwić się o wykorzystanie czasu jako losowego ziarna.
źródło
random_device
, która w niektórych przypadkach może zostać całkowicie złamana . Ponadto,mt19937
chociaż 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.minstd
będzie taka metoda), ale to postęp. Co do słabej implementacjirandom_device
- to okropne i powinno być uznane za błąd (prawdopodobnie także standardu C ++, jeśli na to pozwala).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ć konstruowaniemrandom_device
/mt19937
/uniform_int_distribution
w ścisłej pętli / funkcji wbudowanej? Czy wolę raczej je przekazywać?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_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 liczb
int
równomiernie rozmieszczonych w [-57, 365]. Użyłem nowych funkcji standardowego<chrono>
czasu, ponieważ wspomniałeś, że wydajność jest dla ciebie głównym problemem.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:
Jeśli później okaże się, że
int
nie obejmuje zakresu potrzebnego do dystrybucji, można temu zaradzić, zmieniając cośuniform_int_distribution
podobnego (np. Nalong long
):Jeśli później okaże się, że
minstd_rand
generator nie jest wystarczająco wysokiej jakości, można go łatwo wymienić. Na przykład: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:(
x_
Przedrostek oznacza „oczekiwany”)źródło
d
w każdej iteracji z różnymi ograniczeniami? Jak bardzo spowolniłoby to pętlę?Podzielmy problem na dwie części:
n
z zakresu od 0 do (maks-min).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_MAX
naRAND_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/p
gdziep
jest 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 tymp > 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.
źródło
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ą:
Aha, a tutaj jeszcze trochę stręczycielstwa tego generatora, na wypadek gdybyś nie był przekonany, że powinieneś go używać na znacznie gorszym
rand()
:źródło
boost::uniform_int
rozkładu), możesz przekształcić zakresy min max na cokolwiek chcesz i można go obsiać.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.
źródło
RAND_MAX
na,(double) RAND_MAX
aby uniknąć ostrzeżenia o przepełnieniu całkowitoliczbowym.Oto bezstronna wersja, która generuje liczby w
[low, high]
:Jeśli twój zakres jest dość mały, nie ma powodu, aby buforować prawą stronę porównania w
do
pętli.źródło
[0, h)
dla uproszczenia. Wywołanierand()
maRAND_MAX + 1
możliwe wartości zwracane; przyjmowanierand() % h
zwinięć(RAND_MAX + 1) / h
ich do każdej zh
wartości wyjściowych, z wyjątkiem tego, że(RAND_MAX + 1) / h + 1
z nich są mapowane na wartości mniejsze niż(RAND_MAX + 1) % h
(z powodu ostatniego częściowego cyklu przezh
wyjścia). Dlatego usuwamy(RAND_MAX + 1) % h
możliwe wyniki, aby uzyskać bezstronny rozkład.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.
źródło
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]
(minimum maksimum]
[minimum maksimum)
(minimum maksimum)
źródło
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:
Oto mój przykładowy kod:
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.
źródło
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.
Jeśli NUM_DIGIT jest liczbą cyfr w podstawie b, którą można wyodrębnić, to znaczy
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.
źródło
Wzór na to jest bardzo prosty, więc wypróbuj to wyrażenie,
źródło
int num = (int) rand() % (max - min) + min;
Poniższe wyrażenie powinno być bezstronne, jeśli się nie mylę:
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.
źródło
std::floor
zwracadouble
i potrzebujemy tutaj wartości całkowitej. Po prostu rzucałbym doint
zamiast używaćstd::floor
.