Come generare un numero intero casuale da un intervallo


108

Questo è il seguito di una domanda pubblicata in precedenza:

Come generare un numero casuale in C?

Vorrei essere in grado di generare un numero casuale da un intervallo particolare, come da 1 a 6 per imitare i lati di un dado.

Come potrei fare questo?


3
se guardi la seconda risposta alla domanda a cui ti riferisci hai la risposta. rand ()% 6.
Mats Fredriksson

2
Non capivo come funzionasse, quindi ho deciso di fare una domanda separata per chiarezza.
Jamie Keeling

2
Pensiero casuale: se intervistassi una sezione trasversale casuale di programmatori, scopriresti che un numero casuale di loro sta pensando in modo casuale a modi per generare numeri casualmente. Considerando che l'Universo è governato da leggi precise e prevedibili, non è interessante che proviamo a generare le cose in modo più casuale? Domande come questa tendono sempre a far emergere i poster da 10k +.
Armstrongest

2
@Mats rand ()% 6 può restituire uno 0. Non va bene per un dado.
new123456

Puoi contrassegnare stackoverflow.com/a/6852396/419 come risposta accettata invece della risposta collegata ad essa :) Grazie.
Kev

Risposte:


173

Tutte le risposte finora sono matematicamente sbagliate. La restituzione rand() % Nnon fornisce in modo uniforme un numero nell'intervallo a [0, N)meno che non Ndivida la lunghezza dell'intervallo in cui rand()restituisce (cioè è una potenza di 2). Inoltre, non si sa se i moduli di rand()siano indipendenti: è possibile che vadano 0, 1, 2, ..., il che è uniforme ma non molto casuale. L'unica ipotesi che sembra ragionevole formulare è che rand()emetta una distribuzione di Poisson: qualsiasi due sottointervalli non sovrapposti della stessa dimensione sono ugualmente probabili e indipendenti. Per un insieme finito di valori, ciò implica una distribuzione uniforme e garantisce anche che i valori di rand()siano ben distribuiti.

Ciò significa che l'unico modo corretto per modificare l'intervallo di rand()è dividerlo in caselle; ad esempio, se RAND_MAX == 11desideri un intervallo di 1..6, devi assegnarlo {0,1}a 1,{2,3} a 2 e così via. Questi sono intervalli disgiunti, di uguali dimensioni e quindi sono distribuiti in modo uniforme e indipendente.

Il suggerimento di utilizzare la divisione in virgola mobile è matematicamente plausibile, ma in linea di principio soffre di problemi di arrotondamento. Forsedouble è una precisione abbastanza alta per farlo funzionare; forse no. Non lo so e non voglio doverlo capire; in ogni caso, la risposta dipende dal sistema.

Il modo corretto è usare l'aritmetica dei numeri interi. Cioè, vuoi qualcosa di simile al seguente:

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

Il loop è necessario per ottenere una distribuzione perfettamente uniforme. Ad esempio, se ti vengono dati numeri casuali da 0 a 2 e vuoi solo quelli da 0 a 1, continua a tirare finché non ottieni un 2; non è difficile controllare che questo dia 0 o 1 con uguale probabilità. Questo metodo è descritto anche nel link che i nn hanno dato nella loro risposta, sebbene codificato in modo diverso. Sto usando random()piuttosto che rand()in quanto ha una distribuzione migliore (come notato dalla pagina man per rand()).

Se vuoi ottenere valori casuali al di fuori dell'intervallo predefinito [0, RAND_MAX], devi fare qualcosa di complicato. Forse il più opportuno è definire una funzione random_extended()che tira nbit (usando random_at_most()) e ritorna dentro [0, 2**n), quindi applicare random_at_most()con random_extended()al posto di random()(e 2**n - 1al posto di RAND_MAX) per estrarre un valore casuale inferiore a 2**n, supponendo che tu abbia un tipo numerico che può contenere tale un valore. Infine, ovviamente, puoi ottenere valori in [min, max]uso min + random_at_most(max - min), inclusi valori negativi.


1
@ Adamo Rosenfield, @ Ryan Reich: In una questione connessa dove Adamo aveva risposto: stackoverflow.com/questions/137783/... la risposta più upvoted: L'utilizzo di 'modulo' sarebbe poi essere corretto, no? Per generare 1..7 da 1..21, dovrebbe essere usata la procedura descritta da Ryan. Per favore correggimi se sbaglio.
Arvind

1
Dopo un'ulteriore revisione, un altro problema qui è che questo non funzionerà quando max - min > RAND_MAX, il che è più grave del problema che ho affermato sopra (ad esempio VC ++ ha RAND_MAXsolo 32767).
Interjay

2
Il ciclo while potrebbe essere reso più leggibile. Piuttosto che eseguire l'assegnazione nel condizionale, probabilmente vuoi un file do {} while().
theJPster

4
Ehi, questa risposta è citata dal libro Comet OS;) La prima volta che lo vedo in un libro didattico
vpuente

3
È anche citato nel libro OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Capitolo 9, Pagina 4)
rafascar

33

In seguito alla risposta di @Ryan Reich, ho pensato di offrire la mia versione pulita. Il primo controllo dei limiti non è richiesto dato il secondo controllo dei limiti, e l'ho reso iterativo piuttosto che ricorsivo. Restituisce valori nell'intervallo [min, max], dove max >= mine 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
Nota che questo si bloccherà in un ciclo infinito se range> = RAND_MAX. Chiedimi come lo so: /
theJPster

24
Come lo sai!?
Fantastico Mr Fox

1
Nota che stai confrontando un int con un int senza segno (r> = limit). Il problema è facilmente risolvibile creando limitun int (e opzionalmente bucketanche) poiché RAND_MAX / range< INT_MAXe buckets * range<= RAND_MAX. EDIT: ho presentato e modificato la proposta.
rrrrrrrrrrrrrrrr

la soluzione di @Ryan Reich mi dà ancora una distribuzione migliore (meno sbilanciata)
Vladimir

20

Ecco una formula se conosci i valori massimo e minimo di un intervallo e desideri generare numeri compresi nell'intervallo:

r = (rand() % (max + 1 - min)) + min

9
Come notato nella risposta di Ryan, questo produce un risultato parziale.
David Wolever

6
Risultato parziale, potenziale intoverflow con max+1-min.
chux - Ripristina Monica il

1
questo funziona solo con interi min e max. Se il minimo e il massimo sono float, non è possibile eseguire l'operazione%
Taioli Francesco

17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}

Vedi qui per altre opzioni.


2
@ S.Lott - non proprio. Ognuno distribuisce i casi leggermente più alti in modo diverso, tutto qui. La doppia matematica dà l'impressione che ci sia più precisione lì, ma potresti facilmente usare (((max-min+1)*rand())/RAND_MAX)+mine ottenere probabilmente la stessa identica distribuzione (supponendo che RAND_MAX sia abbastanza piccolo rispetto a int per non overflow).
Steve314

4
Questo è leggermente pericoloso: è possibile che questo ritorni (molto raramente) max + 1, se uno dei due rand() == RAND_MAX, o rand()è molto vicino RAND_MAXe gli errori in virgola mobile spingono il risultato finale oltre max + 1. Per sicurezza, dovresti controllare che il risultato sia nel range prima di restituirlo.
Mark Dickinson

1
@Christoph: sono d'accordo RAND_MAX + 1.0. Non sono ancora sicuro che sia abbastanza buono per impedire un max + 1ritorno, però: in particolare, + minalla fine coinvolge un round che potrebbe finire per produrre max + 1valori elevati di rand (). È più sicuro abbandonare del tutto questo approccio e utilizzare l'aritmetica dei numeri interi.
Mark Dickinson

3
Se RAND_MAXè sostituita da RAND_MAX+1.0come suggerisce Christoph, allora credo che questo è sicuro a condizione che l' + minè fatto usando aritmetica intera: return (unsigned int)((max - min + 1) * scaled) + min. Il motivo (non ovvio) è che, assumendo IEEE 754 aritmetica e round-half-to-even, (e anche questo max - min + 1è esattamente rappresentabile come double, ma sarà vero su una macchina tipica), è sempre vero che x * scaled < xper qualsiasi doppio positivo xe qualsiasi doppio scaledsoddisfacente 0.0 <= scaled && scaled < 1.0.
Mark Dickinson

1
Non riesce randr(0, UINT_MAX): genera sempre 0.
chux - Ripristina Monica il

12

Non faresti semplicemente:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

%è l'operatore modulo. Essenzialmente dividerà solo per 6 e restituirà il resto ... da 0 a 5


1
Fornirà risultati da 1 a 6. Ecco a cosa serve il + 1.
Armstrongest

4
Simon, mostrami una libc in uso ovunque in cui rand()includa i bit di ordine inferiore dello stato del generatore (se utilizza un LCG). Non ne ho visto uno finora: tutti (sì, incluso MSVC con RAND_MAX che è solo 32767) rimuovono i bit di ordine inferiore. L'uso del modulo non è consigliato per altri motivi, vale a dire che distorce la distribuzione a favore di numeri più piccoli.
Joey

@Johannes: Quindi è sicuro che le slot machine non usano il modulo?
Armstrongest

Come escluderei uno 0? Sembra che se lo eseguo in un loop di 30, forse la seconda o la terza volta che viene eseguito ci sia uno 0 circa a metà. È una specie di colpo di fortuna?
Jamie Keeling

@Johannes: Forse al giorno d'oggi non è tanto un problema, ma tradizionalmente non è consigliabile utilizzare i bit di ordine inferiore. c-faq.com/lib/randrange.html
jamesdlin

9

Per coloro che comprendono il problema del bias ma non sopportano il tempo di esecuzione imprevedibile dei metodi basati sul rifiuto, questa serie produce un numero intero casuale progressivamente meno distorto [0, n-1]nell'intervallo:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...

Lo fa sintetizzando un numero casuale di i * log_2(RAND_MAX + 1)bit a virgola fissa ad alta precisione (dov'è iil numero di iterazioni) ed eseguendo una lunga moltiplicazione pern .

Quando il numero di bit è sufficientemente grande rispetto a n , il bias diventa incommensurabilmente piccolo.

Non importa se RAND_MAX + 1è minore di n(come in questa domanda ), o se non è una potenza di due, ma bisogna fare attenzione per evitare un intero overflow se RAND_MAX * nè grande.


2
RAND_MAXè spesso INT_MAX, quindi RAND_MAX + 1-> UB (come INT_MIN)
chux - Reinstate Monica il

@chux è quello che intendo per "bisogna fare attenzione per evitare l'overflow di numeri interi se RAND_MAX * nè grande". È necessario organizzare l'utilizzo di tipi appropriati per le proprie esigenze.
sh1

@chux " RAND_MAXè spesso INT_MAX" Sì, ma solo su sistemi a 16 bit! Qualsiasi architettura ragionevolmente moderna porrà INT_MAX2 ^ 32/2 e RAND_MAX2 ^ 16 / 2. È un'ipotesi errata?
gatto

2
@cat Testato oggi 2 intcompilatori a 32 bit , ho trovato RAND_MAX == 32767su uno e RAND_MAX == 2147483647su un altro. La mia esperienza complessiva (decenni) è quella RAND_MAX == INT_MAXpiù spesso. Quindi non sono d'accordo sul fatto che un'architettura a 32 bit ragionevolmente moderna avrà sicuramente un RAND_MAXa 2^16 / 2. Dal momento che la specifica C lo consente 32767 <= RAND_MAX <= INT_MAX, io codice a quello comunque piuttosto che una tendenza.
chux - Ripristina Monica

3
Ancora coperto da "è necessario prestare attenzione per evitare l'overflow di numeri interi".
sh1

4

Per evitare il bias modulo (suggerito in altre risposte) puoi sempre usare:

arc4random_uniform(MAX-MIN)+MIN

Dove "MAX" è il limite superiore e "MIN" è il limite inferiore. Ad esempio, per numeri compresi tra 10 e 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Soluzione semplice e migliore rispetto all'utilizzo di "rand ()% N".


1
Woohoo, questo è un miliardo di volte migliore delle altre risposte. Vale la pena notare che devi #include <bsd/stdlib.h>prima. Inoltre, qualche idea su come ottenerlo su Windows senza MinGW o CygWin?
gatto

1
No, non è di per sé migliore delle altre risposte, perché le altre risposte sono più generiche. Qui sei limitato ad arc4random, le altre risposte ti permettono di scegliere una diversa fonte casuale, operare con diversi tipi di numero, ... e non da ultimo potrebbero aiutare qualcuno a capire il problema. Non dimenticare che la domanda è interessante anche per altre persone che potrebbero avere dei requisiti speciali o non avere accesso ad arc4random ... Tuttavia, se hai accesso ad essa e desideri una soluzione rapida, è davvero un'ottima risposta 😊
K. Biermann

4

Ecco un algoritmo leggermente più semplice rispetto alla soluzione di Ryan Reich:

/// 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 + 1può facilmente traboccare intaggiunta. In tal caso, (RAND_MAX + 1) % rangegenererà risultati discutibili. Considera(RAND_MAX + (uint32_t)1)
chux - Ripristina Monica il

2

Sebbene Ryan abbia ragione, la soluzione può essere molto più semplice in base a ciò che è noto sulla fonte della casualità. Per ribadire il problema:

  • C'è una fonte di casualità, che emette numeri interi nell'intervallo [0, MAX) con distribuzione uniforme.
  • L'obiettivo è produrre numeri interi casuali distribuiti uniformemente nell'intervallo in [rmin, rmax]cui 0 <= rmin < rmax < MAX.

Nella mia esperienza, se il numero di contenitori (o "scatole") è significativamente inferiore all'intervallo dei numeri originali e la fonte originale è crittograficamente forte, non c'è bisogno di passare attraverso tutto quel rigamarolo, e la semplice divisione modulo lo farebbe sufficiente (come output = rnd.next() % (rmax+1), se rmin == 0), e produrre numeri casuali che sono distribuiti uniformemente "abbastanza", e senza alcuna perdita di velocità. Il fattore chiave è la fonte della casualità (cioè, bambini, non provateci a casa conrand() ).

Ecco un esempio / prova di come funziona nella pratica. Volevo generare numeri casuali da 1 a 22, con una fonte crittograficamente forte che produceva byte casuali (basata su Intel RDRAND). I risultati sono:

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%

Questo è il più vicino all'uniforme di cui ho bisogno per il mio scopo (lancio dei dadi equo, generazione di codebook crittograficamente forti per macchine di cifratura della Seconda Guerra Mondiale come http://users.telenet.be/d.rijmenants/en/kl-7sim.htm , ecc. ). L'output non mostra alcun pregiudizio apprezzabile.

Ecco la fonte del generatore di numeri casuali crittograficamente forte (vero): Intel Digital Random Number Generator e un codice di esempio che produce numeri casuali a 64 bit (senza segno).

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

L'ho compilato su Mac OS X con clang-6.0.1 (diretto) e con gcc-4.8.3 utilizzando il flag "-Wa, q" (perché GAS non supporta queste nuove istruzioni).


Compilato con gcc randu.c -o randu -Wa,q(GCC 5.3.1 su Ubuntu 16) o clang randu.c -o randu(Clang 3.8.0) funziona, ma scarica il core in fase di esecuzione con Illegal instruction (core dumped). Qualche idea?
gatto

Innanzitutto, non so se la tua CPU supporta effettivamente l'istruzione RDRAND. Il tuo sistema operativo è abbastanza recente, ma la CPU potrebbe non esserlo. Secondo (ma questo è meno probabile) - Non ho idea del tipo di assemblatore che Ubuntu include (e Ubuntu tende ad essere abbastanza indietro rispetto all'aggiornamento dei pacchetti). Controlla il sito Intel a cui ho fatto riferimento per verificare se la tua CPU supporta RDRAND.
Mouse

Hai davvero dei buoni punti. Quello che ancora non riesco a capire è cosa c'è di sbagliato rand(). Ho provato alcuni test e pubblicato questa domanda ma non riesco ancora a trovare una risposta definitiva.
myradio

1

Come detto prima il modulo non è sufficiente perché distorce la distribuzione. Ecco il mio codice che maschera i bit e li utilizza per garantire che la distribuzione non sia distorta.

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

}

Il semplice codice seguente ti consente di esaminare la distribuzione:

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

}

Diventa piuttosto inefficiente quando rifiuti i numeri dal rand (). Ciò sarà particolarmente inefficiente quando l'intervallo ha una dimensione che può essere scritta come 2 ^ k + 1. Quindi quasi la metà di tutti i tuoi tentativi da una chiamata lenta a rand () sarà rifiutata dalla condizione. Potrebbe essere meglio calcolare l'intervallo modulo RAND_MAX. Tipo: v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;Capisco che il modulo sia un'operazione molto più lenta del mascheramento, ma continuo a pensare ..... dovrebbe essere testato.
Øystein Schønning-Johansen

rand()restituisce un intnell'intervallo [0..RAND_MAX]. Quell'intervallo può facilmente essere un sottointervallo uint32_te quindi randomInRange(0, ,b)non genera mai valori nell'intervallo (INT_MAX...b].
chux - Ripristina Monica il

0

Restituirà un numero in virgola mobile nell'intervallo [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.