To jest kontynuacja wcześniej opublikowanego pytania:
Jak wygenerować liczbę losową w C?
Chcę mieć możliwość generowania losowej liczby z określonego zakresu, na przykład od 1 do 6, aby naśladować boki kostki.
Jak bym to zrobił?
To jest kontynuacja wcześniej opublikowanego pytania:
Jak wygenerować liczbę losową w C?
Chcę mieć możliwość generowania losowej liczby z określonego zakresu, na przykład od 1 do 6, aby naśladować boki kostki.
Jak bym to zrobił?
Odpowiedzi:
Wszystkie dotychczasowe odpowiedzi są błędne matematycznie. Zwracanie
rand() % N
nie daje w sposób jednolity liczby z zakresu,[0, N)
chyba żeN
dzieli długość interwału, na któryrand()
zwraca (czyli jest potęgą 2). Ponadto nie ma pojęcia, czy modułyrand()
są niezależne: możliwe, że idą0, 1, 2, ...
, co jest jednolite, ale niezbyt przypadkowe. Jedynym założeniem, jakie wydaje się rozsądne, jestrand()
przedstawienie rozkładu Poissona: dowolne dwa nienakładające się podprzedziały o tej samej wielkości są równie prawdopodobne i niezależne. W przypadku skończonego zestawu wartości oznacza to równomierny rozkład, a także zapewnia, że wartościrand()
są ładnie rozproszone.Oznacza to, że jedynym poprawnym sposobem zmiany zakresu
rand()
jest podzielenie go na pola; na przykład, jeśliRAND_MAX == 11
chcesz mieć zakres1..6
, powinieneś przypisać{0,1}
do 1,{2,3}
do 2 i tak dalej. Są to rozłączne, równej wielkości przedziały, a zatem są one równomiernie i niezależnie rozmieszczone.Sugestia użycia dzielenia zmiennoprzecinkowego jest matematycznie wiarygodna, ale w zasadzie ma problemy z zaokrągleniem. Być może
double
jest wystarczająco wysoka precyzja, aby to działało; może nie. Nie wiem i nie chcę tego rozgryzać; w każdym razie odpowiedź zależy od systemu.Poprawnym sposobem jest użycie arytmetyki liczb całkowitych. Oznacza to, że chcesz coś takiego:
Pętla jest niezbędna, aby uzyskać idealnie równomierny rozkład. Na przykład, jeśli otrzymałeś losowe liczby od 0 do 2 i chcesz mieć tylko te od 0 do 1, po prostu ciągnij, aż nie otrzymasz 2; nietrudno sprawdzić, czy daje to 0 lub 1 z równym prawdopodobieństwem. Ta metoda jest również opisana w linku, który nos podał w swojej odpowiedzi, chociaż jest inaczej zakodowany. Używam
random()
raczej niżrand()
ponieważ ma lepszą dystrybucję (jak zauważono na stronie podręcznikarand()
).Jeśli chcesz uzyskać losowe wartości spoza domyślnego zakresu
[0, RAND_MAX]
, musisz zrobić coś trudnego. Być może najbardziej celowe jest, aby zdefiniować funkcjęrandom_extended()
, która ściągan
bity (za pomocąrandom_at_most()
) i zwraca się[0, 2**n)
, a następnie stosujerandom_at_most()
sięrandom_extended()
w miejscurandom()
(i2**n - 1
zamiastRAND_MAX
), aby pociągnąć losową wartość poniżej2**n
, zakładając, że masz typ liczbowy, który może pomieścić takie wartość. Wreszcie, oczywiście, możesz uzyskać wartości w[min, max]
użyciumin + random_at_most(max - min)
, w tym wartości ujemne.źródło
max - min > RAND_MAX
jest to poważniejsze niż problem, który opisałem powyżej (np. VC ++ maRAND_MAX
tylko 32767).do {} while()
.Kontynuując odpowiedź @Ryan Reich, pomyślałem, że zaoferuję moją oczyszczoną wersję. Pierwsze sprawdzenie granic nie jest wymagane, biorąc pod uwagę drugie sprawdzenie granic, i zrobiłem to raczej iteracyjnie niż rekurencyjnie. Zwraca wartości z zakresu [min, max], gdzie
max >= min
i1+max-min < RAND_MAX
.źródło
limit
int (i opcjonalniebucket
również), ponieważRAND_MAX / range
<INT_MAX
ibuckets * range
<=RAND_MAX
. EDYCJA: przesłałem i edytuję propozycję.Oto formuła, jeśli znasz maksymalne i minimalne wartości zakresu i chcesz wygenerować liczby zawierające się między zakresem:
źródło
int
przepełnienie zmax+1-min
.Zobacz tutaj, aby uzyskać inne opcje.
źródło
(((max-min+1)*rand())/RAND_MAX)+min
i uzyskać prawdopodobnie dokładnie ten sam rozkład (zakładając, że RAND_MAX jest wystarczająco mały w stosunku do wartości int, aby nie przepełnić).max + 1
, jeśli jeden z nichrand() == RAND_MAX
lubrand()
jest bardzo blisko,RAND_MAX
a błędy zmiennoprzecinkowe wypychają wynik końcowymax + 1
. Aby być bezpiecznym, przed zwróceniem należy sprawdzić, czy wynik mieści się w zakresie.RAND_MAX + 1.0
. Nadal nie jestem pewien, czy to wystarczy, aby zapobiecmax + 1
zwrotowi: w szczególności+ min
na końcu obejmuje rundę, która może zakończyć się produkcjąmax + 1
dużych wartości rand (). Bezpieczniej jest całkowicie zrezygnować z tego podejścia i zastosować arytmetykę liczb całkowitych.RAND_MAX
otrzymujeRAND_MAX+1.0
jak sugeruje Christoph, to wierzę, że to jest bezpieczne pod warunkiem, że+ min
odbywa się za całkowitą arytmetyczny:return (unsigned int)((max - min + 1) * scaled) + min
. (Nieoczywistym) powodem jest to, że zakładając arytmetykę IEEE 754 i zaokrąglenie od połowy do parzystej (a także tomax - min + 1
jest dokładnie reprezentowane jako podwójna, ale będzie to prawdą na typowej maszynie), zawsze jest prawdą, żex * scaled < x
dla każde pozytywne podwójnex
i każde podwójnescaled
satysfakcjonujące0.0 <= scaled && scaled < 1.0
.randr(0, UINT_MAX)
: zawsze generuje 0.Czy nie zrobiłbyś po prostu:
%
jest operatorem modułu. Zasadniczo podzieli przez 6 i zwróci resztę ... od 0 do 5źródło
rand()
zawiera najmniej znaczące bity stanu generatora (jeśli używa LCG). Jak dotąd nie widziałem żadnego - wszystkie z nich (tak, w tym MSVC z RAND_MAX wynoszącym zaledwie 32767) usuwają bity o najniższej kolejności. Używanie modułu nie jest zalecane z innych powodów, a mianowicie, że wypacza rozkład na korzyść mniejszych liczb.Dla tych, którzy rozumieją problem błędu systematycznego, ale nie znoszą nieprzewidywalnego czasu wykonywania metod opartych na odrzucaniu, ta seria generuje losową liczbę całkowitą z mniejszą tendencją w
[0, n-1]
przedziale:Czyni to poprzez syntezę losowej liczby
i * log_2(RAND_MAX + 1)
bitów o ustalonej precyzji (gdziei
jest liczbą iteracji) i wykonanie długiego mnożenia przezn
.Gdy liczba bitów jest wystarczająco duża w porównaniu z
n
, odchylenie staje się niezmiernie małe.Nie ma znaczenia, czy
RAND_MAX + 1
jest mniejsze niżn
(jak w tym pytaniu ), czy też nie jest to potęga dwójki, ale należy uważać, aby uniknąć przepełnienia liczb całkowitych, jeśliRAND_MAX * n
jest duże.źródło
RAND_MAX
jest częstoINT_MAX
, więcRAND_MAX + 1
-> UB (jak INT_MIN)RAND_MAX * n
jest duży”. Musisz zorganizować użycie odpowiednich typów dla swoich wymagań.RAND_MAX
często brzmiINT_MAX
" Tak, ale tylko w systemach 16-bitowych! Każda rozsądnie nowoczesna architektura ustawiINT_MAX
na 2 ^ 32/2 iRAND_MAX
2 ^ 16 / 2. Czy to jest błędne założenie?int
kompilatory, znalazłemRAND_MAX == 32767
na jednym iRAND_MAX == 2147483647
na drugim. Moje ogólne doświadczenie (dekady) jest takie, żeRAND_MAX == INT_MAX
częściej. Tak zgadzam się, że rozsądnie nowoczesny 32-bitowa architektura z pewnością mająRAND_MAX
na2^16 / 2
. Ponieważ specyfikacja C na to pozwala32767 <= RAND_MAX <= INT_MAX
, i tak koduję to raczej niż tendencję.Aby uniknąć odchylenia modulo (sugerowanego w innych odpowiedziach), zawsze możesz użyć:
Gdzie „MAX” to górna granica, a „MIN” to dolna granica. Na przykład dla liczb od 10 do 20:
Proste rozwiązanie i lepsze niż używanie "rand ()% N".
źródło
#include <bsd/stdlib.h>
najpierw. Masz też jakiś pomysł, jak to zrobić w systemie Windows bez MinGW lub CygWin?Oto nieco prostszy algorytm niż rozwiązanie Ryana Reicha:
źródło
RAND_MAX + 1
można łatwo dodać przelewint
. W takim przypadku(RAND_MAX + 1) % range
wygeneruje wątpliwe wyniki. Rozważ(RAND_MAX + (uint32_t)1)
Chociaż Ryan ma rację, rozwiązanie może być znacznie prostsze w oparciu o to, co wiadomo o źródle losowości. Aby ponownie przedstawić problem:
[0, MAX)
z równomiernym rozkładem.[rmin, rmax]
którym0 <= rmin < rmax < MAX
.Z mojego doświadczenia wynika, że jeśli liczba pojemników (lub „pudełek”) jest znacznie mniejsza niż zakres oryginalnych liczb, a oryginalne źródło jest kryptograficznie mocne - nie ma potrzeby przechodzenia przez wszystkie te rygory, a prosty podział modulo wystarczy (jak
output = rnd.next() % (rmax+1)
, jeślirmin == 0
) i generuje liczby losowe, które są rozmieszczone równomiernie „wystarczająco” i bez utraty szybkości. Kluczowym czynnikiem jest źródło losowości (tj. Dzieci, nie próbuj tego w domurand()
).Oto przykład / dowód, jak to działa w praktyce. Chciałem wygenerować losowe liczby od 1 do 22, mając silne kryptograficznie źródło, które generuje losowe bajty (w oparciu o Intel RDRAND). Wyniki są następujące:
Jest to tak bliskie jednorodności, jak potrzebuję do mojego celu (uczciwy rzut kostką, generowanie silnych kryptograficznie książek kodów dla maszyn szyfrujących z II wojny światowej, takich jak http://users.telenet.be/d.rijmenants/en/kl-7sim.htm itp. ). Wyjście nie wykazuje żadnego znaczącego odchylenia.
Oto źródło silnego kryptograficznie (prawdziwego) generatora liczb losowych: Cyfrowy generator liczb losowych Intel i przykładowy kod, który generuje 64-bitowe (bez znaku) liczby losowe.
Skompilowałem go na Mac OS X z clang-6.0.1 (prosto) iz gcc-4.8.3 używając flagi "-Wa, q" (ponieważ GAS nie obsługuje tych nowych instrukcji).
źródło
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 na Ubuntu 16) lubclang randu.c -o randu
(Clang 3.8.0) działa, ale zrzuca rdzeń w czasie wykonywania zIllegal instruction (core dumped)
. Jakieś pomysły?rand()
. Wypróbowałem kilka testów i opublikowałem to pytanie, ale nie mogę jeszcze znaleźć ostatecznej odpowiedzi.Jak powiedziano wcześniej, modulo nie wystarczy, ponieważ wypacza dystrybucję. Oto mój kod, który maskuje bity i używa ich, aby upewnić się, że dystrybucja nie jest wypaczona.
Poniższy prosty kod pozwala spojrzeć na dystrybucję:
źródło
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
Rozumiem, że modulo to znacznie wolniejsza operacja niż maskowanie, ale nadal uważam, że ..... powinno zostać przetestowane.rand()
zwraca wartośćint
z zakresu[0..RAND_MAX]
. Ten zakres może łatwo być podzakresem,uint32_t
a następnierandomInRange(0, ,b)
nigdy nie generuje wartości w zakresie(INT_MAX...b]
.Zwróci liczbę zmiennoprzecinkową z zakresu [0,1]:
źródło