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 że N
dzieli długość interwału, na który rand()
zwraca (czyli jest potęgą 2). Ponadto nie ma pojęcia, czy moduły rand()
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, jest rand()
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ści rand()
są ładnie rozproszone.
Oznacza to, że jedynym poprawnym sposobem zmiany zakresu rand()
jest podzielenie go na pola; na przykład, jeśli RAND_MAX == 11
chcesz mieć zakres 1..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:
#include <stdlib.h> // For random(), RAND_MAX
// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
unsigned long
// max <= RAND_MAX < ULONG_MAX, so this is okay.
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do {
x = random();
}
// This is carefully written not to overflow
while (num_rand - defect <= (unsigned long)x);
// Truncated division is intentional
return x/bin_size;
}
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ęcznika rand()
).
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ąga n
bity (za pomocą random_at_most()
) i zwraca się [0, 2**n)
, a następnie stosuje random_at_most()
się random_extended()
w miejscu random()
(i 2**n - 1
zamiast RAND_MAX
), aby pociągnąć losową wartość poniżej 2**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życiu min + random_at_most(max - min)
, w tym wartości ujemne.
max - min > RAND_MAX
jest to poważniejsze niż problem, który opisałem powyżej (np. VC ++ ma RAND_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], gdziemax >= min
i 1+max-min < RAND_MAX
.
unsigned int rand_interval(unsigned int min, unsigned int max)
{
int r;
const unsigned int range = 1 + max - min;
const unsigned int buckets = RAND_MAX / range;
const unsigned int limit = buckets * range;
/* Create equal size buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
do
{
r = rand();
} while (r >= limit);
return min + (r / buckets);
}
limit
int (i opcjonalnie bucket
również), ponieważ RAND_MAX / range
< INT_MAX
i buckets * 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 = (rand() % (max + 1 - min)) + min
int
przepełnienie z max+1-min
.
unsigned int
randr(unsigned int min, unsigned int max)
{
double scaled = (double)rand()/RAND_MAX;
return (max - min +1)*scaled + min;
}
Zobacz tutaj, aby uzyskać inne opcje.
(((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 nich rand() == RAND_MAX
lub rand()
jest bardzo blisko, RAND_MAX
a błędy zmiennoprzecinkowe wypychają wynik końcowy max + 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 zapobiec max + 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
otrzymuje RAND_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 to max - min + 1
jest dokładnie reprezentowane jako podwójna, ale będzie to prawdą na typowej maszynie), zawsze jest prawdą, że x * scaled < x
dla każde pozytywne podwójne x
i każde podwójne scaled
satysfakcjonujące 0.0 <= scaled && scaled < 1.0
.
randr(0, UINT_MAX)
: zawsze generuje 0.
Czy nie zrobiłbyś po prostu:
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
jest operatorem modułu. Zasadniczo podzieli przez 6 i zwróci resztę ... od 0 do 5
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:
r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
Czyni to poprzez syntezę losowej liczby i * log_2(RAND_MAX + 1)
bitów o ustalonej precyzji (gdzie i
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śli RAND_MAX * n
jest duże.
RAND_MAX
jest często INT_MAX
, więc RAND_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 brzmi INT_MAX
" Tak, ale tylko w systemach 16-bitowych! Każda rozsądnie nowoczesna architektura ustawi INT_MAX
na 2 ^ 32/2 i RAND_MAX
2 ^ 16 / 2. Czy to jest błędne założenie?
int
kompilatory, znalazłem RAND_MAX == 32767
na jednym i RAND_MAX == 2147483647
na drugim. Moje ogólne doświadczenie (dekady) jest takie, że RAND_MAX == INT_MAX
częściej. Tak zgadzam się, że rozsądnie nowoczesny 32-bitowa architektura z pewnością mają RAND_MAX
na 2^16 / 2
. Ponieważ specyfikacja C na to pozwala 32767 <= RAND_MAX <= INT_MAX
, i tak koduję to raczej niż tendencję.
Aby uniknąć odchylenia modulo (sugerowanego w innych odpowiedziach), zawsze możesz użyć:
arc4random_uniform(MAX-MIN)+MIN
Gdzie „MAX” to górna granica, a „MIN” to dolna granica. Na przykład dla liczb od 10 do 20:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
Proste rozwiązanie i lepsze niż używanie "rand ()% N".
#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:
/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
uint32_t range = (end - begin) + 1;
uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);
/* Imagine range-sized buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
uint32_t randVal = rand();
while (randVal >= limit) randVal = rand();
/// Return the position you hit in the bucket + begin as random number
return (randVal % range) + begin;
}
Example (RAND_MAX := 16, begin := 2, end := 7)
=> range := 6 (1 + end - begin)
=> limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)
The limit is always a multiple of the range,
so we can split it into range-sized buckets:
Possible-rand-output: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Buckets: [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
Buckets + begin: [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]
1st call to rand() => 13
→ 13 is not in the bucket-range anymore (>= limit), while-condition is true
→ retry...
2nd call to rand() => 7
→ 7 is in the bucket-range (< limit), while-condition is false
→ Get the corresponding bucket-value 1 (randVal % range) and add begin
=> 3
RAND_MAX + 1
można łatwo dodać przelew int
. 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órym 0 <= 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śli rmin == 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:
Rnd distribution test (22 boxes, numbers of entries in each box): 1: 409443 4.55% 2: 408736 4.54% 3: 408557 4.54% 4: 409125 4.55% 5: 408812 4.54% 6: 409418 4.55% 7: 408365 4.54% 8: 407992 4.53% 9: 409262 4.55% 10: 408112 4.53% 11: 409995 4.56% 12: 409810 4.55% 13: 409638 4.55% 14: 408905 4.54% 15: 408484 4.54% 16: 408211 4.54% 17: 409773 4.55% 18: 409597 4.55% 19: 409727 4.55% 20: 409062 4.55% 21: 409634 4.55% 22: 409342 4.55% total: 100.00%
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.
int rdrand64_step(unsigned long long int *therand)
{
unsigned long long int foo;
int cf_error_status;
asm("rdrand %%rax; \
mov $1,%%edx; \
cmovae %%rax,%%rdx; \
mov %%edx,%1; \
mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
*therand = foo;
return cf_error_status;
}
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).
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 na Ubuntu 16) lub clang randu.c -o randu
(Clang 3.8.0) działa, ale zrzuca rdzeń w czasie wykonywania z Illegal 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.
static uint32_t randomInRange(uint32_t a,uint32_t b) {
uint32_t v;
uint32_t range;
uint32_t upper;
uint32_t lower;
uint32_t mask;
if(a == b) {
return a;
}
if(a > b) {
upper = a;
lower = b;
} else {
upper = b;
lower = a;
}
range = upper - lower;
mask = 0;
//XXX calculate range with log and mask? nah, too lazy :).
while(1) {
if(mask >= range) {
break;
}
mask = (mask << 1) | 1;
}
while(1) {
v = rand() & mask;
if(v <= range) {
return lower + v;
}
}
}
Poniższy prosty kod pozwala spojrzeć na dystrybucję:
int main() {
unsigned long long int i;
unsigned int n = 10;
unsigned int numbers[n];
for (i = 0; i < n; i++) {
numbers[i] = 0;
}
for (i = 0 ; i < 10000000 ; i++){
uint32_t rand = random_in_range(0,n - 1);
if(rand >= n){
printf("bug: rand out of range %u\n",(unsigned int)rand);
return 1;
}
numbers[rand] += 1;
}
for(i = 0; i < n; i++) {
printf("%u: %u\n",i,numbers[i]);
}
}
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ępnie randomInRange(0, ,b)
nigdy nie generuje wartości w zakresie (INT_MAX...b]
.