Genera numeri casuali in modo uniforme su un intero intervallo


93

Ho bisogno di generare numeri casuali entro un intervallo specificato, [max; min].

Inoltre, i numeri casuali dovrebbero essere distribuiti uniformemente nell'intervallo, non posizionati in un punto particolare.

Attualmente sto generando come:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

Dai miei test, i numeri casuali vengono generati intorno a un solo punto.

Example
min = 3604607;
max = 7654607;

Numeri casuali generati:

3631594
3609293
3630000
3628441
3636376
3621404

Dalle risposte seguenti: OK, RAND_MAX è 32767. Sono sulla piattaforma Windows C ++. Esiste un altro metodo per generare numeri casuali con una distribuzione uniforme?



1
Non avevo idea che il C ++ rand()fosse uniforme. Quale libreria stai usando? cstdlib.h's rand()non è uniforme: cplusplus.com/reference/cstdlib/rand
Mike Warren

3
No, rand () è uniforme (tranne che in alcune prime implementazioni con bug). ciò che non è uniforme è l'utilizzo dell'operatore modulo '%' per restringere l'intervallo. Vedi stackoverflow.com/questions/2999075/… per una soluzione adeguata, o se hai "arc4random_uniform" disponibile, puoi anche usarlo direttamente.
John Meacham

@ Alien01: Prenderesti in considerazione di cambiare la risposta accettata a quella di "Shoe" ("Perché rand è una cattiva idea" ecc ..)? La mia risposta è davvero obsoleta e ogni volta che ricevo un voto positivo mi sento come se qualcuno stesse correndo lungo il corridoio sbagliato.
Peterchen

Bel white paper sulla casualità in c ++ 11.
Pupsik

Risposte:


153

Perché randè una cattiva idea

La maggior parte delle risposte che hai qui fanno uso della randfunzione e dell'operatore modulo. Questo metodo potrebbe non generare numeri in modo uniforme (dipende dall'intervallo e dal valore di RAND_MAX), ed è quindi sconsigliato.

C ++ 11 e generazione su un intervallo

Con C ++ 11 sono aumentate molte altre opzioni. Uno dei quali si adatta alle vostre esigenze, per la generazione di un numero casuale in un intervallo, abbastanza bene: std::uniform_int_distribution. Ecco un esempio:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

Ed ecco l'esempio in corso.

Altri generatori casuali

L' <random>intestazione offre innumerevoli altri generatori di numeri casuali con diversi tipi di distribuzioni tra cui Bernoulli, Poisson e normale.

Come posso mescolare un contenitore?

Lo standard prevede std::shuffle, che può essere utilizzato come segue:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

L'algoritmo riordinerà gli elementi in modo casuale, con una complessità lineare.

Boost.Random

Un'altra alternativa, nel caso in cui non si abbia accesso a un compilatore C ++ 11 +, è usare Boost.Random . La sua interfaccia è molto simile a quella del C ++ 11.


22
PRESTATE ATTENZIONE a questa risposta, poiché è molto più moderna.
gsamaras

Questa è la risposta giusta. Grazie! Tuttavia, mi piacerebbe vedere una descrizione più approfondita di ogni passaggio di quel codice. Ad esempio, cos'è un mt19937tipo?
Apollo

@Apollo La documentazione dice "Mersenne Twister a 32 bit di Matsumoto e Nishimura, 1998". Suppongo che sia un algoritmo per generare numeri pseudo-casuali.
Scarpa

@Shoe, per un determinato intervallo, genera numeri stesso ordine, 1 9 6 2 8 7 1 4 7 7. Sai come randomizzare questo ogni volta che eseguiamo il programma?

1
@Richard Qual è l'alternativa?
Scarpa

59

[modifica] Attenzione: non utilizzare rand()per statistiche, simulazione, crittografia o qualsiasi cosa seria.

È abbastanza buono da far sembrare i numeri casuali per un tipico essere umano di fretta, non di più.

Vedi la risposta di @ Jefffrey per opzioni migliori, o questa risposta per numeri casuali crittografati.


Generalmente, i bit alti mostrano una distribuzione migliore rispetto ai bit bassi, quindi il modo consigliato per generare numeri casuali di un intervallo per scopi semplici è:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Nota : assicurati che RAND_MAX + 1 non trabocchi (grazie Demi)!

La divisione genera un numero casuale nell'intervallo [0, 1); "estendilo" all'intervallo richiesto. Solo quando max-min + 1 si avvicina a RAND_MAX hai bisogno di una funzione "BigRand ()" come pubblicata da Mark Ransom.

Ciò evita anche alcuni problemi di slicing dovuti al modulo, che possono peggiorare ulteriormente i tuoi numeri.


Non è garantito che il generatore di numeri casuali integrato abbia la qualità richiesta per le simulazioni statistiche. Va bene che i numeri "sembrino casuali" a un essere umano, ma per un'applicazione seria, dovresti prendere qualcosa di meglio o almeno controllarne le proprietà (la distribuzione uniforme di solito è buona, ma i valori tendono a correlarsi e la sequenza è deterministica ). Knuth ha un eccellente (anche se difficile da leggere) trattato sui generatori di numeri casuali, e recentemente ho trovato LFSR eccellente e dannatamente semplice da implementare, dato che le sue proprietà sono OK per te.


4
BigRand può dare risultati migliori anche quando l'intervallo desiderato non supera RAND_MAX. Considera quando RAND_MAX è 32767 e desideri 32767 valori possibili: due di questi 32768 numeri casuali (compreso lo zero) verranno mappati sullo stesso output e avranno il doppio delle probabilità che si verifichino rispetto agli altri. Difficilmente una proprietà casuale ideale!
Mark Ransom

7
(RAND_MAX + 1) è una cattiva idea. Questo può rollover e darti un valore negativo. Meglio fare qualcosa come: ((double) RAND_MAX) + 1.0
Demi

3
@ Peterchen: Penso che tu abbia frainteso quello che diceva Demi. Voleva dire questo: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min sposta semplicemente la conversione su raddoppia ed evita il problema.
Mooing Duck

3
Inoltre, questo cambia semplicemente la distribuzione dai 32767 valori più bassi nell'intervallo a 32767 valori distribuiti uniformemente nell'intervallo, ei restanti 4017233 valori non verranno mai selezionati da questo algoritmo.
Mooing Duck

1
La risposta data è off di 1. L'equazione corretta è: ((double) rand () / (RAND_MAX + 1.0)) * (max-min) + min Il "max-min + 1" è usato quando si usa% not * . Vedrai perché quando fai min = 0, max = 1. Potrebbe peterchen o @ peter-mortensen modificarlo.
davepc

17

Vorrei completare le eccellenti risposte di Angry Shoe e Peterchen con una breve panoramica dello stato dell'arte nel 2015:

Alcune buone scelte

randutils

La randutilslibreria (presentazione) è una novità interessante, che offre un'interfaccia semplice e capacità casuali (dichiarate) robuste. Ha gli svantaggi che aggiunge una dipendenza dal tuo progetto e, essendo nuovo, non è stato ampiamente testato. Ad ogni modo, essendo gratuito (licenza MIT) e solo header, penso che valga la pena provare.

Campione minimo: un tiro di dado

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Anche se non si è interessati alla libreria, il sito web ( http://www.pcg-random.org/ ) fornisce molti articoli interessanti sul tema della generazione di numeri casuali in generale e sulla libreria C ++ in particolare.

Boost.Random

Boost.Random (documentazione) è la libreria che ha ispirato C++11's <random>, con il quale condivide gran parte dell'interfaccia. Sebbene teoricamente sia anche una dipendenza esterna, Boostha ormai uno status di libreria "quasi standard" e il suo Randommodulo potrebbe essere considerato come la scelta classica per la generazione di numeri casuali di buona qualità. Presenta due vantaggi rispetto alla C++11soluzione:

  • è più portabile, richiede solo il supporto del compilatore per C ++ 03
  • i suoi random_devicemetodi usi specifici del sistema a offerta semina di buona qualità

L'unico piccolo difetto è che l'offerta del modulo random_devicenon è solo di intestazione, bisogna compilare e collegare boost_random.

Campione minimo: un tiro di dado

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Sebbene il campione minimo funzioni bene, i programmi reali dovrebbero utilizzare un paio di miglioramenti:

  • fare mt19937a thread_local: il generatore è abbastanza grassoccio (> 2 KB) ed è meglio non allocarlo nello stack
  • seme mt19937con più di un numero intero: il Mersenne Twister ha uno stato grande e può beneficiare di una maggiore entropia durante l'inizializzazione

Alcune scelte non proprio buone

La libreria C ++ 11

Pur essendo la soluzione più idiomatica, la <random>libreria non offre molto in cambio della complessità della sua interfaccia anche per le esigenze di base. Il difetto è std::random_device: lo Standard non impone alcuna qualità minima per il suo output (a patto che entropy()ritorni 0) e, a partire dal 2015, MinGW (non il compilatore più utilizzato, ma difficilmente una scelta esoterica) stamperà sempre 4sul campione minimo.

Campione minimo: un tiro di dado

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Se l'implementazione non è marcio, questa soluzione dovrebbe essere equivalente a quella Boost e valgono gli stessi suggerimenti.

La soluzione di Godot

Campione minimo: un tiro di dado

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

Questa è una soluzione semplice, efficace e pulita. Unico difetto, la compilazione richiederà un po 'di tempo - circa due anni, a condizione che C ++ 17 venga rilasciato in tempo e la randintfunzione sperimentale sia approvata nel nuovo standard. Forse a quel punto anche le garanzie sulla qualità della semina miglioreranno.

Il peggio-è-meglio soluzione

Campione minimo: un tiro di dado

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

La vecchia soluzione C è considerata dannosa e per buone ragioni (vedi le altre risposte qui o questa analisi dettagliata ). Tuttavia, ha i suoi vantaggi: è semplice, portabile, veloce e onesto, nel senso che è noto che i numeri casuali che si ottengono sono difficilmente decenti, e quindi non si è tentati di usarli per scopi seri.

La soluzione dei troll contabili

Campione minimo: un tiro di dado

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Sebbene il 9 sia un risultato alquanto insolito per un normale tiro di dado, si deve ammirare l'eccellente combinazione di buone qualità in questa soluzione, che riesce ad essere la più veloce, più semplice, più adatta alla cache e più portatile. Sostituendo 9 con 4 si ottiene un generatore perfetto per qualsiasi tipo di Dungeons and Dragons die, pur evitando i valori carichi di simboli 1, 2 e 3. L'unico piccolo difetto è che, a causa del cattivo umore dei troll contabili di Dilbert, questo programma genera effettivamente un comportamento indefinito.


La randutilslibreria ora si chiama PCG.
tay10r

11

Se RAND_MAXè 32767, puoi raddoppiare facilmente il numero di bit.

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

Non credo che funzioni. I generatori di numeri pseudo casuali sono tipicamente deterministici. Ad esempio, se la prima randchiamata ritorna 0x1234e la seconda 0x5678, allora ottieni 0x12345678. Questo è l' unico numero che puoi ottenere che inizia con 0x1234, perché il numero successivo sarà sempre 0x5678. Ottieni risultati a 32 bit, ma hai solo 32768 numeri possibili.
user694733

@ user694733 un buon generatore di numeri casuali ha un periodo maggiore del numero di output che può generare, quindi 0x1234 non sarà sempre seguito da 0x5678.
Mark Ransom

9

Se puoi, usa Boost . Ho avuto fortuna con la loro libreria casuale .

uniform_int dovrebbe fare quello che vuoi.


Ho lavorato su uniform_int con un merseinne twister e sfortunatamente per alcuni intervalli i valori restituiti da uniform_int non sono così uniformi come mi sarei aspettato. Ad esempio uniform_int <> (0, 3) tende a produrre più 0 di 1 o 2
ScaryAardvark

@ScaryAardvark che suona come una cattiva implementazione di uniform_intallora. È abbastanza facile generare un output imparziale, ci sono state più domande qui che dimostrano il metodo.
Mark Ransom

@Mark Ransom. Sì, sono completamente d'accordo.
ScaryAardvark

8

Se sei preoccupato per la casualità e non per la velocità, dovresti utilizzare un metodo di generazione di numeri casuali sicuro. Ci sono diversi modi per farlo ... Il più semplice è usare il generatore di numeri casuali di OpenSSL .

Puoi anche scrivere il tuo utilizzando un algoritmo di crittografia (come AES ). Selezionando un seme e un IV e quindi ricodificando continuamente l'output della funzione di crittografia. Usare OpenSSL è più facile, ma meno virile.


Non riesco a utilizzare nessuna libreria di terze parti? Sono limitato solo a C ++.
Anand

Quindi segui la strada virile, implementa AES o qualche altro algoritmo di crittografia.
SoapBox

2
RC4 è banale da codificare e abbastanza casuale per tutti gli scopi pratici (tranne WEP, ma non è interamente colpa di RC4). Dico sul serio, è un codice incredibilmente banale. Tipo 20 righe o giù di lì. La voce di Wikipedia ha uno pseudo-codice.
Steve Jessop

4
Perché non puoi usare il codice di terze parti? Se questa è una domanda per i compiti a casa, dovresti dirlo, perché molte persone preferiscono dare suggerimenti utili invece di fornire soluzioni complete in questo caso. Se non è un compito, vai a prendere a calci il ragazzo che dice "nessun codice di terze parti", perché è un deficiente.
DevSolar

Link più diretto alla documentazione della funzione OpenSSL rand (): openssl.org/docs/crypto/rand.html#
DevSolar

5

Dovresti cercare il RAND_MAXtuo particolare compilatore / ambiente. Penso che vedresti questi risultati se rand()producesse un numero casuale a 16 bit. (sembra che tu stia assumendo che sarà un numero a 32 bit).

Non posso promettere che questa sia la risposta, ma per favore pubblica il tuo valore RAND_MAXe qualche dettaglio in più sul tuo ambiente.



2

Questo non è il codice, ma questa logica può aiutarti.

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

Se vuoi che i numeri siano distribuiti uniformemente sull'intervallo, dovresti suddividere l'intervallo in un numero di sezioni uguali che rappresentano il numero di punti di cui hai bisogno. Quindi ottieni un numero casuale con un minimo / massimo per ogni sezione.

Come altra nota, probabilmente non dovresti usare rand()perché non è molto bravo a generare numeri casuali. Non so su quale piattaforma stai eseguendo, ma probabilmente esiste una funzione migliore che puoi chiamare come random().


1

Ciò dovrebbe fornire una distribuzione uniforme sull'intervallo [low, high)senza utilizzare float, a condizione che l'intervallo complessivo sia inferiore a RAND_MAX.

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

e per valori maggiori di RAND_MAX vuoi qualcosa di simile

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

Questo è più o meno come std :: uniform_int_distribution fa le cose.


0

Per loro natura, un piccolo campione di numeri casuali non deve essere distribuito uniformemente. Sono casuali, dopotutto. Sono d'accordo che se un generatore di numeri casuali genera numeri che sembrano essere raggruppati in modo coerente, probabilmente c'è qualcosa che non va.

Ma tieni presente che la casualità non è necessariamente uniforme.

Modifica: ho aggiunto "piccolo campione" per chiarire.


"distribuito uniformemente" ha un significato ben definito e i generatori casuali standard di solito si avvicinano.
peterchen

Sì, hai ragione, i generatori di numeri casuali dovrebbero produrre un output che nel tempo è generalmente uniforme nella sua distribuzione. Immagino che il mio punto sia che su un piccolo numero di istanze (6 come mostrato nell'esempio) l'output non sarà sempre uniforme.
Kluge

Kluge ha ragione. Distribuzione uniforme in un piccolo campione indica che il campione è sicuramente non è casuale.
Bill the Lizard

1
Bill, non indica niente del genere. Piccoli campioni sono per lo più privi di significato, ma se si suppone che l'RNG sia uniforme e l'output è uniforme, perché è peggio di un piccolo campione non uniforme?
Dan Dyer

2
Distribuzioni significative in entrambi i casi indicano non casualità: penso che Bill significhi semplicemente che anche 6 risultati equidistanti sarebbero sospetti. Nell'OP, 6 valori si trovano in un intervallo di 32k / 4M, o <1% dell'intervallo desiderato. La probabilità che questo sia un falso positivo è troppo piccola per discuterne.
Steve Jessop,

0

La soluzione data da man 3 rand per un numero compreso tra 1 e 10 inclusi è:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

Nel tuo caso, sarebbe:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

Naturalmente, questa non è perfetta casualità o uniformità come fanno notare altri messaggi, ma è sufficiente per la maggior parte dei casi.


1
Questo si limita a riorganizzare la distribuzione in modo che appaia più uniforme, ma in realtà non lo è più nemmeno per i grandi intervalli (come il caso dell'OP)
Mooing Duck

0

@Soluzione ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Attenzione : non dimenticare a causa dell'estensione e dei possibili errori di precisione (anche se RAND_MAX fosse abbastanza grande), sarai in grado di generare solo "bin" distribuiti uniformemente e non tutti i numeri in [min, max].


@Solution: Bigrand

Attenzione : Nota che questo raddoppia i bit, ma non sarà comunque in grado di generare tutti i numeri nel tuo intervallo in generale, cioè, non è necessariamente vero che BigRand () genererà tutti i numeri compresi nel suo intervallo.


Informazioni : il tuo approccio (modulo) è "buono" fintanto che l'intervallo di rand () supera l'intervallo e rand () è "uniforme". L'errore per al massimo i primi numeri max - min è 1 / (RAND_MAX +1).

Inoltre, suggerisco di passare al nuovo pacchetto random e anche in C ++ 11, che offre migliori e più varietà di implementazioni rispetto a rand ().


0

Questa è la soluzione che ho trovato:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

Questa è una soluzione bucket, concettualmente simile alle soluzioni che utilizzano rand() / RAND_MAXper ottenere un intervallo in virgola mobile tra 0-1 e poi arrotondarlo in un bucket. Tuttavia, utilizza la matematica puramente intera e sfrutta la pavimentazione a divisione intera per arrotondare il valore al bucket più vicino.

Fa alcune supposizioni. Innanzitutto, si presume che RAND_MAX * (max - min + 1)si adatterà sempre a un file int32_t. Se RAND_MAXè 32767 e vengono utilizzati calcoli int a 32 bit, l'intervallo massimo che puoi avere è 32767. Se la tua implementazione ha un RAND_MAX molto più grande, puoi ovviare usando un numero intero più grande (come int64_t) per il calcolo. In secondo luogo, if int64_tviene utilizzato ma RAND_MAXè ancora 32767, a intervalli maggiori diRAND_MAX inizierai a ottenere "buchi" nei possibili numeri di output. Questo è probabilmente il problema più grande con qualsiasi soluzione derivata dal ridimensionamento rand().

Il test su un numero enorme di iterazioni mostra tuttavia che questo metodo è molto uniforme per piccoli intervalli. Tuttavia, è possibile (e probabile) che matematicamente questo abbia qualche piccolo pregiudizio e possibilmente sviluppi problemi quando l'intervallo si avvicina RAND_MAX. Provalo tu stesso e decidi se soddisfa le tue esigenze.


-1

Ovviamente, il codice seguente non ti darà numeri casuali ma un numero pseudo casuale. Usa il codice seguente

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

Per esempio:

int myRand = QUICK_RAND(10, 20);

Devi chiamare

srand(time(0));  // Initialize random number generator.

altrimenti i numeri non saranno casuali.


1
La domanda è chiedere una distribuzione uniforme. Questa soluzione proposta non produrrà una distribuzione uniforme. La libreria standard C ++ dispone di funzionalità per la generazione di numeri pseudo-casuali . Coloro che fanno fornire distribuzione uniforme, se richiesto.
Rilevabile il

-3

L'ho appena trovato su Internet. Questo dovrebbe funzionare:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

Per favore, chiarisci a cosa ti servono, ci sono tonnellate di algoritmi per PRNG là fuori. Inoltre, sarebbe più facile se modifichi la tua domanda principale invece di pubblicare risposte.
Peterchen

Questo funziona meglio per me ... sono in grado di ottenere numeri casuali distribuiti meglio con questa formula ..
anand

4
Se il tuo intervallo supera RAND_MAX, i risultati potrebbero non essere uniformi. Cioè, ci sono valori nell'intervallo che non saranno rappresentati, non importa quante volte chiamate la tua funzione.
dmckee --- gattino ex moderatore

4
Inoltre, se max e min sono entrambi int senza segno e min è 0 e max è MAX_UINT, ((max) - (min) +1) sarà 0 e il risultato sarà sempre 0. Fai attenzione all'overflow facendo questo tipo di matematica! Come notato da dmckee, questo estende la distribuzione sull'intervallo di destinazione, ma non garantisce più di RAND_MAX valori univoci.
jesup
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.