Come generare numeri casuali su C++

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:

Con queste basi e un po’ di pratica si possono generare facilmente numeri casuali di alta qualità in C++ per qualsiasi esigenza.