Generazione di numeri interi casuali da un intervallo


158

Ho bisogno di una funzione che generi un numero intero casuale in un determinato intervallo (compresi i valori dei bordi). Non ho irragionevoli requisiti di qualità / casualità, ho quattro requisiti:

  • Ho bisogno che sia veloce. Il mio progetto deve generare milioni (o talvolta anche decine di milioni) di numeri casuali e la mia attuale funzione di generatore ha dimostrato di essere un collo di bottiglia.
  • Ho bisogno che sia ragionevolmente uniforme (l'uso di rand () va benissimo).
  • gli intervalli min-max possono essere compresi tra <0, 1> e <-32727, 32727>.
  • deve essere seminabile.

Al momento ho il seguente codice C ++:

output = min + (rand() * (int)(max - min) / RAND_MAX)

Il problema è che non è davvero uniforme - viene restituito max solo quando rand () = RAND_MAX (per Visual C ++ è 1/32727). Questo è un grosso problema per piccoli intervalli come <-1, 1>, dove l'ultimo valore non viene quasi mai restituito.

Quindi ho preso carta e penna e ho trovato la seguente formula (che si basa sul trucco di arrotondamento intero (int) (n + 0,5)):

inserisci qui la descrizione dell'immagine

Ma non mi dà ancora una distribuzione uniforme. Ripetizioni ripetute con 10000 campioni mi danno un rapporto di 37:50:13 per valori valori -1, 0. 1.

Potresti suggerire una formula migliore? (o anche l'intera funzione del generatore di numeri pseudo-casuali)



3
@Bill MaGriff: si. Ha lo stesso problema. Una versione semplificata è: come puoi dividere uniformemente 10 pezzi di caramelle tra 3 bambini (senza rompere nessuna delle caramelle)? La risposta è che non puoi - devi dare tre a ogni bambino e non dare il decimo a nessuno.
Jerry Coffin,

5
Hai visto Boost.Random ?
Fred Nurk,

3
Leggi l'articolo di Andrew Koenig "Un semplice problema che non viene quasi mai risolto correttamente": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev,

1
@Gene Bushuyev: Sia Andrew che io stiamo trattando questo argomento da un po 'di tempo ormai. Vedi: groups.google.com/group/comp.lang.c++/browse_frm/thread/… e: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Risposte:


105

È una soluzione distribuita veloce, un po 'migliore della tua, ma ancora non uniformemente distribuita

output = min + (rand() % static_cast<int>(max - min + 1))

Tranne quando la dimensione dell'intervallo è una potenza di 2, questo metodo produce numeri distribuiti non uniformi di parte indipendentemente dalla qualità di rand(). Per un test completo della qualità di questo metodo, leggi questo .


2
Grazie, questo sembra essere abbastanza buono per me dai test rapidi: la sua distribuzione per -1, 0, 1 è quasi 33:33:33.
Matěj Zábský,

3
Restituisce sempre il valore massimo. Mi sto perdendo qualcosa? : |
rohan-patel,

15
rand()dovrebbe essere considerato dannoso in C ++ ci sono modi molto migliori per ottenere qualcosa che sia uniformemente distribuito e in realtà casuale.
Mgetz,

1
Restituisce davvero un numero corretto entro l'intervallo del 100% delle volte? Ho trovato qualche altro StackOverflow risposta qui che sta utilizzando la ricorsione per farlo "nel modo giusto": stackoverflow.com/a/6852396/623622
Czarek Tomczak

2
Dal momento che è una risposta altamente votata (del desiderata), che sembra fonte affidabile di informazioni per molti nuovi lettori, penso che sia molto importante menzionare la qualità e i potenziali pericoli di questa soluzione, quindi ho fatto una modifica.
plasmacel,

297

La risposta C ++ più semplice (e quindi migliore) (utilizzando lo standard 2011) è

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

Non c'è bisogno di reinventare la ruota. Non c'è bisogno di preoccuparsi dei pregiudizi. Non c'è bisogno di preoccuparsi di usare il tempo come seme casuale.


1
Oggi questa dovrebbe essere la risposta . Riferimento di generazione di numeri pseudo-casuali per più funzionalità.
alextoind,

8
Concordo sul "più semplice" (e il più idiomatico), non sul "migliore". Sfortunatamente lo Standard non fornisce alcuna garanzia random_device, che in alcuni casi potrebbe essere completamente rotto . Inoltre, mt19937pur essendo un'ottima scelta di uso generale, non è il più veloce tra i generatori di buona qualità (si veda questo confronto ) e pertanto potrebbe non essere il candidato ideale per il PO.
Alberto M,

1
@AlbertoM Sfortunatamente, il confronto a cui ti riferisci non fornisce abbastanza dettagli e non è riproducibile, il che lo rende discutibile (inoltre, è del 2015, mentre la mia risposta risale al 2013). Potrebbe essere vero che ci sono metodi migliori in circolazione (e si spera in futuro, minstdsarà un tale metodo), ma questo è un progresso. Per quanto riguarda la scarsa implementazione di random_device- è orribile e dovrebbe essere considerato un bug (possibilmente anche dello standard C ++, se lo consente).
Walter,

1
Sono totalmente d'accordo con te; In realtà non volevo criticare la tua soluzione di per sé , volevo solo avvertire il lettore occasionale che la risposta definitiva sulla questione, nonostante le promesse di C ++ 11, deve ancora essere scritta. Pubblicherò una panoramica sull'argomento a partire dal 2015 come risposta a una domanda correlata .
Alberto M,

1
Questo è "più semplice"? Potresti spiegare perché il chiaramente molto più semplice rand()non è un'opzione, ed è importante per un uso non critico, come la generazione di un indice pivot casuale? Inoltre, devo preoccuparmi di costruire random_device/ mt19937/ uniform_int_distributionin una funzione a circuito chiuso / in linea? Preferirei piuttosto passarli in giro?
bluenote10,

60

Se il tuo compilatore supporta C ++ 0x e il suo utilizzo è un'opzione per te, <random>è probabile che la nuova intestazione standard soddisfi le tue esigenze. Ha un'alta qualità uniform_int_distributionche accetterà limiti minimi e massimi (inclusi quelli di cui hai bisogno), e puoi scegliere tra vari generatori di numeri casuali da collegare a quella distribuzione.

Ecco il codice che genera un milione di casuali intdistribuiti uniformemente in [-57, 365]. Ho usato le nuove <chrono>strutture standard per cronometrarlo mentre hai menzionato che le prestazioni sono una delle principali preoccupazioni per te.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Per me (Intel Core i5 a 2,8 GHz) questo stampa:

2.10268e + 07 numeri casuali al secondo.

Puoi eseguire il seeding del generatore passando un int al suo costruttore:

    G g(seed);

Se successivamente scopri che intnon copre l'intervallo di cui hai bisogno per la tua distribuzione, questo può essere risolto cambiando in questo uniform_int_distributionmodo (es. A long long):

    typedef std::uniform_int_distribution<long long> D;

Se in seguito scopri che minstd_randnon è un generatore di qualità abbastanza alta, può anche essere facilmente sostituito. Per esempio:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

Avere il controllo separato sul generatore di numeri casuali e la distribuzione casuale può essere abbastanza liberatorio.

Ho anche calcolato (non mostrato) i primi 4 "momenti" di questa distribuzione (usando minstd_rand) e li ho confrontati con i valori teorici nel tentativo di quantificare la qualità della distribuzione:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

(Il x_prefisso si riferisce a "previsto")


3
Questa risposta potrebbe utilizzare un breve frammento di codice di riepilogo che mostra solo il codice effettivamente necessario per generare un numero intero casuale da un intervallo.
Arekolek,

Il problema è reso più semplice dal fatto che min e max della distribuzione non cambiano mai. E se dovessi creare dad ogni iterazione con limiti diversi? Quanto rallenterebbe il ciclo?
quant_dev

16

Dividiamo il problema in due parti:

  • Genera un numero casuale nnell'intervallo da 0 a (max-min).
  • Aggiungi min a quel numero

La prima parte è ovviamente la più difficile. Supponiamo che il valore di ritorno di rand () sia perfettamente uniforme. L'uso di modulo aggiungerà distorsione ai primi (RAND_MAX + 1) % (max-min+1)numeri. Quindi se potessimo cambiare magicamente RAND_MAXin RAND_MAX - (RAND_MAX + 1) % (max-min+1), non ci sarebbe più alcun pregiudizio.

Si scopre che possiamo usare questa intuizione se siamo disposti a consentire lo pseudo-non determinismo nel tempo di esecuzione del nostro algoritmo. Ogni volta che rand () restituisce un numero troppo grande, chiediamo semplicemente un altro numero casuale fino a quando non ne otteniamo uno sufficientemente piccolo.

Il tempo di esecuzione è ora distribuito geometricamente , con valore atteso 1/pdove pè la probabilità di ottenere un numero abbastanza piccolo al primo tentativo. Poiché RAND_MAX - (RAND_MAX + 1) % (max-min+1)è sempre inferiore a (RAND_MAX + 1) / 2, lo sappiamo p > 1/2, quindi il numero previsto di iterazioni sarà sempre inferiore a due per qualsiasi intervallo. Con questa tecnica dovrebbe essere possibile generare decine di milioni di numeri casuali in meno di un secondo su una CPU standard.

MODIFICARE:

Sebbene quanto sopra sia tecnicamente corretto, la risposta di DSimon è probabilmente più utile in pratica. Non dovresti implementare queste cose da solo. Ho visto molte implementazioni del campionamento del rifiuto ed è spesso molto difficile capire se è corretto o meno.


Per completezza: questo è il campionamento di rifiuto .
Etarion,

3
Curiosità: Joel Spolsky una volta menzionò una versione di questa domanda come esempio di ciò che StackOverflow era bravo a rispondere. Ho guardato attraverso le risposte sul campionamento rifiuto sito che coinvolgono in quel momento e ogni singolo uno non era corretto.
Jørgen Fogh

13

Che ne dici del Mersenne Twister ? L'implementazione di boost è piuttosto facile da usare ed è ben testata in molte applicazioni del mondo reale. L'ho usato io stesso in diversi progetti accademici come l'intelligenza artificiale e gli algoritmi evolutivi.

Ecco il loro esempio in cui fanno una semplice funzione per tirare un dado a sei facce:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

Oh, ed ecco un po 'più di sfruttamento di questo generatore nel caso in cui non sei convinto che dovresti usarlo su quello ampiamente inferiore rand():

Il Mersenne Twister è un generatore di "numeri casuali" inventato da Makoto Matsumoto e Takuji Nishimura; il loro sito web include numerose implementazioni dell'algoritmo.

Essenzialmente, il Mersenne Twister è un registro a scorrimento a feedback lineare molto ampio. L'algoritmo opera su un seed a 19.937 bit, memorizzato in un array di 624 elementi di numeri interi senza segno a 32 bit. Il valore 2 ^ 19937-1 è un numero primo di Mersenne; la tecnica per manipolare il seme si basa su un vecchio algoritmo di "torsione", da cui il nome "Mersenne Twister".

Un aspetto interessante di Mersenne Twister è il suo uso di operazioni binarie - al contrario della moltiplicazione che richiede tempo - per generare numeri. L'algoritmo ha anche un periodo molto lungo e una buona granularità. È rapido ed efficace per applicazioni non crittografiche.


1
Il twister di Mersenne è un buon generatore, ma il problema che sta affrontando rimane, indipendentemente dal generatore sottostante stesso.
Jerry Coffin,

Non voglio usare Boost solo per il generatore casuale, perché (dato che il mio progetto è una libreria) significa introdurre un'altra dipendenza nel progetto. Probabilmente sarò costretto ad usarlo comunque in futuro, quindi posso passare a questo generatore.
Matěj Zábský,

1
@Jerry Coffin Quale problema? L'ho offerto perché soddisfaceva tutti i suoi requisiti: è veloce, uniforme (usando la boost::uniform_intdistribuzione), puoi trasformare le gamme min min in qualsiasi cosa ti piaccia ed è seminabile.
Aphex,

@mzabsky Probabilmente non avrei lasciato che ciò mi fermasse, quando ho dovuto spedire i miei progetti ai miei professori per l'invio, ho appena incluso i file di intestazione boost che stavo usando; non dovresti dover impacchettare l'intera libreria boost 40mb con il tuo codice. Naturalmente nel tuo caso questo potrebbe non essere fattibile per altri motivi come il copyright ...
Aphex,

@Aphex Il mio progetto non è in realtà un simulatore scientifico o qualcosa che necessita di una distribuzione davvero uniforme. Ho usato il vecchio generatore per 1,5 anni senza alcun problema, ho notato la distribuzione parziale solo quando ne avevo bisogno per generare numeri da un intervallo molto piccolo (3 in questo caso). Tuttavia, la velocità è ancora argomento per considerare la soluzione boost. Esaminerò la sua licenza per vedere se posso aggiungere i pochi file necessari al mio progetto - mi piace il "Checkout -> F5 -> pronto per l'uso" come è ora.
Matěj Zábský,

11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Questa è una mappatura di 32768 numeri interi su (nMax-nMin + 1) numeri interi. La mappatura sarà abbastanza buona se (nMax-nMin + 1) è piccolo (come richiesto). Si noti tuttavia che se (nMax-nMin + 1) è grande, la mappatura non funzionerà (ad esempio, non è possibile mappare i valori 32768 su 30000 valori con uguale probabilità). Se sono necessari tali intervalli, è necessario utilizzare una sorgente casuale a 32 o 64 bit, anziché i risultati rand () a 15 bit, oppure ignorare i risultati rand () che non rientrano nell'intervallo.


Nonostante la sua impopolarità, questo è anche quello che uso per i miei progetti non scientifici. Facile da capire (non è necessaria una laurea in matematica) e si comporta adeguatamente (non è mai stato necessario profilare alcun codice utilizzandolo). :) In caso di grandi intervalli, immagino che potremmo mettere insieme due valori di rand () e ottenere un valore di 30 bit con cui lavorare (supponendo che RAND_MAX = 0x7fff, ovvero 15 bit casuali)
efotinis

passare RAND_MAXa (double) RAND_MAXper evitare avvisi di overflow di numeri interi.
alex

4

Ecco una versione imparziale che genera numeri in [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Se il tuo intervallo è ragionevolmente piccolo, non c'è motivo di memorizzare nella cache il lato destro del confronto nel doloop.


IMO, nessuna delle soluzioni presentate è davvero molto migliorata. La sua soluzione basata su loop funziona, ma probabilmente è abbastanza inefficiente, specialmente per un piccolo intervallo come discusso dall'OP. La sua soluzione di deviazione uniforme in realtà non produce affatto deviati uniformi . Al massimo mimetizza la mancanza di uniformità.
Jerry Coffin,

@Jerry: controlla la nuova versione.
Jeremiah Willcock,

Sono un po 'incerto sul fatto che funzioni correttamente. Potrebbe, ma la correttezza non sembra ovvia, almeno per me.
Jerry Coffin,

@Jerry: ecco il mio ragionamento: supponiamo che l'intervallo sia [0, h)per semplicità. La chiamata rand()ha RAND_MAX + 1possibili valori di ritorno; portando i loro rand() % hcrolli (RAND_MAX + 1) / hsu ciascuno dei hvalori di output, tranne che (RAND_MAX + 1) / h + 1su di essi sono mappati su valori inferiori a (RAND_MAX + 1) % h(a causa dell'ultimo ciclo parziale attraverso gli houtput). Pertanto rimuoviamo i (RAND_MAX + 1) % hpossibili output per ottenere una distribuzione imparziale.
Jeremiah Willcock,

3

Raccomando la libreria Boost.Random , super dettagliata e ben documentata, che consente di specificare esplicitamente quale distribuzione si desidera e in scenari non crittografici può effettivamente superare l' implementazione tipica di una libreria C.


1

supponiamo che min e max siano valori int, [e] significa includere questo valore, (e) significa non includere questo valore, usando sopra per ottenere il valore giusto usando c ++ rand ()

riferimento: per () [] definisci, visita:

https://en.wikipedia.org/wiki/Interval_(mathematics)

per la funzione rand e srand o RAND_MAX definisci, visitare:

http://en.cppreference.com/w/cpp/numeric/random/rand

[minimo Massimo]

int randNum = rand() % (max - min + 1) + min

(minimo Massimo]

int randNum = rand() % (max - min) + min + 1

[minimo Massimo)

int randNum = rand() % (max - min) + min

(minimo Massimo)

int randNum = rand() % (max - min - 1) + min + 1

0

In questo thread è stato già discusso il campionamento del rifiuto, ma volevo suggerire un'ottimizzazione basata sul fatto che rand() % 2^somethingnon introduce alcun pregiudizio come già menzionato sopra.

L'algoritmo è davvero semplice:

  • calcola la potenza minima di 2 maggiore della lunghezza dell'intervallo
  • randomizza un numero in quel "nuovo" intervallo
  • restituisce quel numero se è inferiore alla lunghezza dell'intervallo originale
    • respingere altrimenti

Ecco il mio codice di esempio:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

Questo funziona bene soprattutto per piccoli intervalli, perché la potenza di 2 sarà "più vicina" alla lunghezza dell'intervallo reale, e quindi il numero di missioni sarà minore.

PS
Ovviamente evitare la ricorsione sarebbe più efficiente (non è necessario calcolare più e più volte il massimale del tronco ..) ma ho pensato che fosse più leggibile per questo esempio.


0

Si noti che nella maggior parte dei suggerimenti il ​​valore casuale iniziale ottenuto dalla funzione rand (), che in genere va da 0 a RAND_MAX, viene semplicemente sprecato. Stai creando un solo numero casuale da esso, mentre esiste una solida procedura che può darti di più.

Supponiamo di voler [min, max] regione di numeri casuali interi. Partiamo da [0, max-min]

Prendi base b = max-min + 1

Inizia da rappresentare un numero ottenuto da Rand () nella base b.

In questo modo hai il floor (log (b, RAND_MAX) perché ogni cifra nella base b, tranne forse l'ultima, rappresenta un numero casuale nell'intervallo [0, max-min].

Naturalmente lo spostamento finale su [min, max] è semplice per ogni numero casuale r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Se NUM_DIGIT è il numero di cifre nella base b che è possibile estrarre e cioè

NUM_DIGIT = floor(log(b,RAND_MAX))

quindi quanto sopra è una semplice implementazione dell'estrazione di NUM_DIGIT numeri casuali da 0 a b-1 su un numero casuale RAND_MAX che fornisce b <RAND_MAX.


-1

La formula per questo è molto semplice, quindi prova questa espressione,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

2
L'intero problema era l'utilizzo del rand di C / C ++ che restituiva numeri interi in un intervallo specificato dal runtime. Come dimostrato in questo thread, la mappatura di numeri casuali da [0, RAND_MAX] a [MIN, MAX] non è del tutto semplice, se si desidera evitare di distruggere le loro proprietà statistiche o prestazioni. Se hai raddoppia nell'intervallo [0, 1], la mappatura è semplice.
Matěj Zábský,

2
La tua risposta è sbagliata, invece dovresti usare il modulo:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes,

-2

La seguente espressione dovrebbe essere imparziale se non sbaglio:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Sto assumendo qui che rand () ti dia un valore casuale nell'intervallo tra 0,0 e 1,0 NON compreso 1,0 e che max e min siano numeri interi con la condizione che min <max.


std::floorritorna doublee qui abbiamo bisogno di un valore intero. Vorrei solo lanciare intinvece di utilizzare std::floor.
musiphil,
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.