Jak wygenerować losową liczbę całkowitą z zakresu


108

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ł?


3
jeśli spojrzysz na drugą odpowiedź na zadane pytanie, masz odpowiedź. rand ()% 6.
Mats Fredriksson

2
Nie rozumiałem, jak to działa, więc postanowiłem zadać osobne pytanie dla jasności.
Jamie Keeling

2
Losowa myśl: jeśli odpytałeś losowo przekrojową grupę programistów, zobaczyłbyś, że losowa liczba z nich losowo myśli o sposobach losowego generowania liczb. Biorąc pod uwagę, że Wszechświat rządzi się precyzyjnymi i przewidywalnymi prawami, czy nie jest interesujące, że próbujemy generować rzeczy w sposób bardziej losowy? Takie pytania zawsze wywołują ponad 10 000 plakatów.
Armstrongest

2
@Mats rand ()% 6 może zwrócić 0. Niedobra kostka.
nowy123456

Czy możesz oznaczyć stackoverflow.com/a/6852396/419 jako zaakceptowaną odpowiedź zamiast odpowiedzi, która do niej prowadzi :) Dzięki.
Kev,

Odpowiedzi:


173

Wszystkie dotychczasowe odpowiedzi są błędne matematycznie. Zwracanie rand() % Nnie daje w sposób jednolity liczby z zakresu, [0, N)chyba że Ndzieli 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 == 11chcesz 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 doublejest 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 nbity (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 - 1zamiast 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.


1
@Adam Rosenfield, @ Ryan Reich: W pokrewnym pytaniu, na które Adam odpowiedział: stackoverflow.com/questions/137783/ ... najbardziej pozytywna odpowiedź: użycie „modułu” byłoby zatem niepoprawne, nie? Aby wygenerować 1..7 z 1..21, należy zastosować procedurę opisaną przez Ryana. Proszę mnie poprawić, jeśli się mylę.
Arvind

1
Po dalszej analizie, inną kwestią jest to, że to nie zadziała, gdy max - min > RAND_MAXjest to poważniejsze niż problem, który opisałem powyżej (np. VC ++ ma RAND_MAXtylko 32767).
interjay

2
Pętla while może być bardziej czytelna. Zamiast wykonywać przypisanie warunkowe, prawdopodobnie chcesz do {} while().
TheJPster

4
Hej, ta odpowiedź jest cytowana w książce Comet OS;) Pierwszy raz widzę to w książce do nauki
vpuente

3
Jest również cytowany w książce OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Rozdział 9, strona 4)
rafascar

33

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

28
Zauważ, że utknie to w nieskończonej pętli, jeśli zakres> = RAND_MAX. Zapytaj mnie, skąd wiem: /
theJPster

24
Skąd wiesz!?
Fantastyczny pan Fox

1
Zauważ, że porównujesz int z unsigned int (r> = limit). Problem można łatwo rozwiązać, tworząc limitint (i opcjonalnie bucketrównież), ponieważ RAND_MAX / range< INT_MAXi buckets * range<= RAND_MAX. EDYCJA: przesłałem i edytuję propozycję.
rrrrrrrrrrrrrrrr

rozwiązanie od @Ryana Reicha nadal daje mi lepszą (mniej stronniczą) dystrybucję
Vladimir

20

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

9
Jak zauważono w odpowiedzi Ryana, daje to stronniczy wynik.
David Wolever,

6
Wynik tendencyjny, potencjalne intprzepełnienie z max+1-min.
chux - Przywróć Monikę

1
działa to tylko z liczbami całkowitymi min i max. Jeśli min i max są
zmienne

17
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.


2
@ S.Lott - nie bardzo. Każdy inaczej rozkłada przypadki z nieco wyższymi szansami, to wszystko. Podwójna matematyka sprawia wrażenie, że jest tam większa precyzja, ale równie łatwo można by użyć (((max-min+1)*rand())/RAND_MAX)+mini 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ć).
Steve314

4
Jest to nieco niebezpieczne: jest możliwe (bardzo rzadko) powrót max + 1, jeśli jeden z nich rand() == RAND_MAXlub rand()jest bardzo blisko, RAND_MAXa 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.
Mark Dickinson,

1
@Christoph: Zgadzam się RAND_MAX + 1.0. Nadal nie jestem pewien, czy to wystarczy, aby zapobiec max + 1zwrotowi: w szczególności + minna końcu obejmuje rundę, która może zakończyć się produkcją max + 1dużych wartości rand (). Bezpieczniej jest całkowicie zrezygnować z tego podejścia i zastosować arytmetykę liczb całkowitych.
Mark Dickinson

3
Jeśli RAND_MAXotrzymuje RAND_MAX+1.0jak sugeruje Christoph, to wierzę, że to jest bezpieczne pod warunkiem, że + minodbywa 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 + 1jest dokładnie reprezentowane jako podwójna, ale będzie to prawdą na typowej maszynie), zawsze jest prawdą, że x * scaled < xdla każde pozytywne podwójne xi każde podwójne scaledsatysfakcjonujące 0.0 <= scaled && scaled < 1.0.
Mark Dickinson

1
Niepowodzenie randr(0, UINT_MAX): zawsze generuje 0.
chux - Przywróć Monikę

12

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


1
Daje wyniki od 1 do 6. Do tego służy + 1.
Armstrongest

4
Simon, pokaż mi używaną bibliotekę libc w dowolnym miejscu, która 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.
Joey

@Johannes: Więc można śmiało powiedzieć, że automaty do gier nie używają modułu?
Armstrongest

Jak wykluczyć 0? Wygląda na to, że jeśli uruchomię go w pętli 30, może za drugim lub trzecim razem jest 0 mniej więcej w połowie. Czy to jakiś przypadek?
Jamie Keeling

@Johannes: Może w dzisiejszych czasach nie jest to aż tak duży problem, ale tradycyjnie używanie mniej znaczących bitów nie jest zalecane. c-faq.com/lib/randrange.html
jamesdlin

9

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 ijest 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 + 1jest 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 * njest duże.


2
RAND_MAXjest często INT_MAX, więc RAND_MAX + 1-> UB (jak INT_MIN)
chux - Przywróć Monikę

@chux to właśnie mam na myśli mówiąc o „należy uważać, aby uniknąć przepełnienia całkowitoliczbowego, jeśli RAND_MAX * njest duży”. Musisz zorganizować użycie odpowiednich typów dla swoich wymagań.
sh1

@chux " RAND_MAXczęsto brzmi INT_MAX" Tak, ale tylko w systemach 16-bitowych! Każda rozsądnie nowoczesna architektura ustawi INT_MAXna 2 ^ 32/2 i RAND_MAX2 ^ 16 / 2. Czy to jest błędne założenie?
kot

2
@cat Przetestowałem dzisiaj 2 32-bitowe intkompilatory, znalazłem RAND_MAX == 32767na jednym i RAND_MAX == 2147483647na drugim. Moje ogólne doświadczenie (dekady) jest takie, że RAND_MAX == INT_MAXczęściej. Tak zgadzam się, że rozsądnie nowoczesny 32-bitowa architektura z pewnością mają RAND_MAXna 2^16 / 2. Ponieważ specyfikacja C na to pozwala 32767 <= RAND_MAX <= INT_MAX, i tak koduję to raczej niż tendencję.
chux - Przywróć Monikę

3
Wciąż objęty klauzulą ​​„należy uważać, aby uniknąć przepełnienia liczb całkowitych”.
sh1

4

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".


1
Woohoo, to jest miliard razy lepsze niż inne odpowiedzi. Warto zauważyć, że musisz #include <bsd/stdlib.h>najpierw. Masz też jakiś pomysł, jak to zrobić w systemie Windows bez MinGW lub CygWin?
kot

1
Nie, sama w sobie nie jest lepsza niż inne odpowiedzi, ponieważ inne odpowiedzi są bardziej ogólne. Tutaj jesteś ograniczony do arc4random, inne odpowiedzi pozwalają ci wybrać inne losowe źródło, operować różnymi typami liczb ... i wreszcie mogą pomóc komuś zrozumieć problem. Nie zapominaj, że pytanie jest również interesujące dla innych osób, które mogą mieć jakieś specjalne wymagania lub nie mieć dostępu do arc4random ... Niemniej jednak, jeśli masz do niego dostęp i chcesz szybkiego rozwiązania, jest to naprawdę bardzo dobra odpowiedź 😊
K. Biermann

4

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

1
RAND_MAX + 1można łatwo dodać przelew int. W takim przypadku (RAND_MAX + 1) % rangewygeneruje wątpliwe wyniki. Rozważ(RAND_MAX + (uint32_t)1)
chux - Przywróć Monikę

2

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:

  • Istnieje źródło losowości, wyprowadzanie liczb całkowitych w zakresie [0, MAX) z równomiernym rozkładem.
  • Celem jest utworzenie równomiernie rozłożonych losowych liczb całkowitych w zakresie, w [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).


Skompilowany z 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?
kot

Po pierwsze, nie wiem, czy twój procesor faktycznie obsługuje instrukcję RDRAND. Twój system operacyjny jest dość nowy, ale procesor może nie być. Po drugie (ale jest to mniej prawdopodobne) - nie mam pojęcia, jaki rodzaj asemblera zawiera Ubuntu (a Ubuntu ma tendencję do dość wstecznego aktualizowania pakietów). Sprawdź witrynę Intela, do której się odwołałem, aby sprawdzić, czy twój procesor obsługuje RDRAND.
Mysz

Masz naprawdę dobre strony. To, czego wciąż nie mogę dostać, to to, w czym jest tak źle rand(). Wypróbowałem kilka testów i opublikowałem to pytanie, ale nie mogę jeszcze znaleźć ostatecznej odpowiedzi.
myradio

1

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]);
    }

}

Staje się dość nieefektywne, gdy odrzucasz liczby z rand (). Będzie to szczególnie nieefektywne, gdy zakres ma rozmiar, który można zapisać jako 2 ^ k + 1. Wtedy prawie połowa wszystkich twoich prób z powolnego wywołania rand () zostanie odrzucona przez warunek. Czy nie byłoby lepiej obliczyć zakres modulo RAND_MAX. Na przykład: 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.
Øystein Schønning-Johansen

rand()zwraca wartość intz zakresu [0..RAND_MAX]. Ten zakres może łatwo być podzakresem, uint32_ta następnie randomInRange(0, ,b)nigdy nie generuje wartości w zakresie (INT_MAX...b].
chux - Przywróć Monikę

0

Zwróci liczbę zmiennoprzecinkową z zakresu [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.