I numeri casuali sono essenziali in molte applicazioni di programmazione, dai giochi ai software di simulazione scientifica. C++ offre diversi modi per generare numeri pseudo-casuali in maniera efficiente ed efficace.
In questa guida completa analizzeremo i vari metodi disponibili e come implementarli correttamente nel codice C++.
Panoramica sulla generazione di numeri casuali
Prima di scendere nei dettagli tecnici, diamo un’occhiata ai concetti di base.
I numeri generati casualmente da un computer non sono mai completamente casuali. Vengono chiamati numeri pseudo-casuali perché sono il risultato di algoritmi deterministici che simulano una sequenza casuale a partire da un valore iniziale chiamato “seme”.
Cambiando il seme si ottengono sequenze diverse.
La qualità di un generatore di numeri casuali dipende da:
- Casualità – la sequenza deve essere imprevedibile e non presentare schemi evidenti.
- Lunghezza periodo – il numero di valori generabili prima che la sequenza si ripeta. Periodi più lunghi sono preferibili.
- Prestazioni – la velocità di generazione di numeri casuali deve essere adeguata all’applicazione.
In C++ abbiamo a disposizione generatori di numeri casuali di ottima qualità che soddisfano questi criteri. Vediamoli in dettaglio.
Generatori di numeri casuali standard in C++
C++ mette a disposizione diversi generatori di numeri pseudo-casuali tramite le librerie standard e . I più comuni sono:
std::default_random_engine
Questo è il generatore predefinito in C++, basato sull’algoritmo Mersenne Twister. Ha un lungo periodo di 2^19937-1 e produce numeri con una distribuzione uniforme molto vicina al caso reale.
Si inizializza specificando il tipo di motore, ad esempio:
std::default_random_engine generator;
std::mt19937
Versione specifica del Mersenne Twister con periodo di 2^19937-1. Molto efficace e con ottima qualità di casualità.
std::mt19937 generator;
### std::mt19937_64
Versione a 64 bit del Mersenne Twister con periodo 2^19937-1. Utile per applicazioni che richiedono una precisione maggiore.
std::mt19937_64 generator;
### std::random_device
Questo generatore sfrutta l’entropia disponibile nell’hardware del computer per produrre numeri imprevedibili. Utile per generare semi forti da usare con gli altri generatori.
std::random_device r;
unsigned int random_seed = r(); // ottiene un numero casuale da usare come seme
### std::shuffle
Mescola gli elementi di una sequenza in modo casuale. Utile per permutazioni casuali.
std::vector myVector;
// …riempi vettore
std::shuffle(myVector.begin(), myVector.end(), generator); // mescola vettore
Questi generatori coprono la maggior parte delle esigenze di generazione casuale in C++. Per applicazioni avanzate sono disponibili anche altri motori specifici.
Tecniche di generazione di numeri casuali in C++
Ora che abbiamo visto i generatori disponibili, analizziamo alcune tecniche comuni per generare diversi tipi di numeri casuali in C++.
Generare numeri interi casuali
Per generare numeri interi casuali in un intervallo, utilizziamo le distribuzioni casuali fornite da C++. Ad esempio per numeri tra 1 e 6, per simulare un lancio di dado a 6 facce:
std::default_random_engine generator;
std::uniform_int_distribution distribution(1,6);
int roll = distribution(generator); // tira un dado a 6 facce
uniform_int_distribution
accetta in input l’intervallo desiderato. Possiamo riutilizzare lo stesso generatore e specificare intervalli diversi per ottenere altri tipi di numeri interi.
Generare numeri floating-point casuali
Per i numeri a virgola mobile, usiamo uniform_real_distribution
:
std::uniform_real_distribution distribution(1.5, 6.8);
double number = distribution(generator);
Questo genera numeri double casuali tra 1.5 e 6.8 uniformi. Intervalli diversi sono possibili specificando i parametri min e max.
Generare numeri casuali da una distribuzione normale
La distribuzione normale, o gaussiana, è comune in ambito statistico. In C++ la generiamo così:
std::normal_distribution distribution(mean, stddev);
double randomNumber = distribution(generator);
Dove mean
e stddev
specificano media e deviazione standard della distribuzione.
Numeri casuali in un tipo personalizzato
Si possono generare anche numeri casuali per un tipo personalizzato, ad esempio una struct:
struct MyType {
int x;
int y;
};
// generatore che accetta MyType
std::uniform_int_distribution distribution(min, max);
MyType random = distribution(generator);
Dove min
e max
sono istanze di MyType che rappresentano i valori estremi.
Altre distribuzioni disponibili
Oltre alle distribuzioni viste, C++ mette a disposizione:
- std::bernoulli_distribution – per booleani casuali
- std::poisson_distribution – per eventi rari
- std::binomial_distribution – per prove binomiali
- std::geometric_distribution – per distribuzione geometrica
- std::…
e molte altre per usi specifici. Consulta la documentazione per i dettagli.
Estrarre numeri in C++: Procedure consigliate
Vediamo ora alcune buone pratiche per generare numeri casuali robusti e di qualità in C++.
Inizializzare il generatore con un seme casuale
Invece di usare sempre lo stesso seme, generiamo un valore casuale ad ogni esecuzione:
std::random_device r;
unsigned seed = r();
std::mt19937 generator(seed);
Questo rende la sequenza imprevedibile.
Usare std::shuffle per mescolamenti casuali
Preferire std::shuffle
rispetto a generare permutazioni manualmente: è più efficiente e produce migliore qualità di casualità.
Evitare sementi deboli
Evitare semi prevedibili come l’ora del sistema. Meglio usare std::random_device
.
Usare la miglior risoluzione disponibile
Preferire generatori a 64 bit per numeri floating point. Evitare il troncamento a 32 bit.
Testare la qualità dei numeri
Verificare che la sequenza generata superi test statistici di casualità per rilevare difetti.
Seguendo queste best practice si ottengono numeri pseudo-casuali di alta qualità con C++, ideali per applicazioni scientifiche e giochi.
Generazione di numeri casuali in applicazioni reali
Vediamo ora alcuni esempi concreti di utilizzo di numeri casuali in programmi C++.
Simulazioni
Nelle simulazioni sono spesso necessarie variabili aleatorie per rappresentare fenomeni stocastici. Ad esempio in una simulazione fisica:
std::default_random_engine generator;
std::normal_distribution distribution(5.0, 2.0); // media e deviaz std
double velocity = distribution(generator); // velocità casuale
double position = integral(velocity); // calcola posizione
Questo simula una velocità variabile casualmente.
Giochi
I giochi fanno ampio uso di numeri casuali, ad esempio nei giochi di carte:
std::shuffle(deck.begin(), deck.end(), generator); // mescola il mazzo
int cardIndex = std::uniform_int_distribution(0, deck.size() - 1);
Card drawnCard = deck[cardIndex]; // pesca una carta
Oppure nei giochi di ruolo, per i danni:
std::default_random_engine generator;
int attackDamage = std::uniform_int_distribution(min, max);
enemy.hitPoints -= attackDamage;
I numeri casuali rendono ogni partita diversa.
Crittografia
I generatori hardware come std::random_device
producono entropia per generare chiavi crittografiche sicure:
std::random_device r;
std::uniform_int_distribution dist;
uint64_t key = dist(r); // chiave casuale sicura
L’imprevedibilità è essenziale per la crittografia.
Questi sono solo alcuni esempi di applicazioni reali che fanno affidamento su numeri casuali di qualità. C++ mette a disposizione tutti gli strumenti necessari per implementare generatori robusti ed efficienti.
Risorse utili
Abbiamo coperto molti aspetti della generazione di numeri casuali in C++, ma ci sono ancora molte features avanzate da esplorare. Ecco alcune risorse per approfondire:
- Pagina di riferimento sul sito cppreference.com;
- Linee guida sul random number generation del C++ Standards Committee;
- PDF Numeri casuali C++, corso di programmazione Università degli Studi di Catania;
Con queste basi e un po’ di pratica si possono generare facilmente numeri casuali di alta qualità in C++ per qualsiasi esigenza.