La generazione di numeri random è una funzionalità importante in molti programmi e applicazioni, come videogiochi, crittografia, simulazioni e intelligenza artificiale. In questo articolo vedremo come generare numeri pseudo-casuali in C++ utilizzando la funzione rand() e il più moderno std::random della libreria <random> introdotta con C++11.
Indice
- La funzione rand() e il valore RAND_MAX
- Generare un numero random compreso in un intervallo: l’operatore modulo (%)
- Il numero casuale è sempre lo stesso!
- Generare un numero random in C++ utilizzando srand() e time()
- Superare i limiti di rand() con il metodo std::random
- Generare numeri casuali in virgola mobile
- Conclusione
La funzione rand() e il valore RAND_MAX
Vediamo come generare numeri casuali in C++ mediante la funzione rand() la quale viene utilizzata per generare un numero compreso nell’intervallo tra 0 e RAND_MAX, dove RAND_MAX è un valore che cambia a seconda del compilatore usato (in genere 32767).
Nell’esempio che segue vediamo come generare un numero compreso tra 0 e 999:
#include<iostream>
#include<cstdlib>
using namespace std;
int main()
{
// genero un numero casuale compreso tra 0 e 999
cout<<rand()%1000<<endl;
}
Generare un numero random compreso in un intervallo: l’operatore modulo (%)
L’istruzione rand()%1000
genera un numero intero compreso tra 0 e 999. Se utilizziamo l’istruzione rand()%N
, infatti, otterremo un numero compresi tra 0 ed N-1.
Per impostare un massimo si ricorre all’operatore modulo (%) che, come sappiamo, serve per calcolare il resto di una divisione intera e questo spiega perché il valore massimo può essere N-1! Facciamo un esempio: un qualsiasi numero diviso per 5 può dare come resto 0, 1, 2, 3 e 4… non può dare 5 perché altrimenti avremmo una divisione intera senza resto!
Qualche esempio:
rand()%2 // genera un numero compreso tra 0 e 1
rand()%100 // genera un numero compreso tra 0 e 99
rand()%43 // genera un numero compreso tra 0 e 42
rand()%10+5 // genera un numero compreso tra 5 e 14
Nell’ultima istruzione abbiamo aggiunto +5 (questo numero prende il nome di offset) a entrambi i numeri dell’intervallo (il minore ed il maggiore).
Il numero casuale è sempre lo stesso!
Proviamo a salvare, compilare il codice visto all’inizio di questo articolo e ad eseguire il programma. Noterete che tutte le volte che eseguite il codice otterrete sempre lo stesso numero! Ma com’è possibile? Non doveva essere un generatore di numeri casuali??
La spiegazione di questo fenomeno è legata alla funzione rand()
: questa, infatti, non genera un numero realmente casuale ma, semplicemente, restituisce il prossimo valore pseudo-casuale nella sequenza di valori che va da 0 a RAND_MAX. Per ottenere un numero casuale, quindi, è necessario cambiare il punto iniziale (seme) di quella sequenza usando srand()
.
La pseudo-casualità è proprio questo: il risultato generato dall’algoritmo sembra casuale ma in realtà non lo è. E’ prevedibile.
Generare un numero random in C++ utilizzando srand() e time()
Per migliorare la casualità del risultato dobbiamo modificare il codice visto in precedenza con le funzioni srand()
e time()
. Vediamo un esempio che prevede l’utilizzo di un semplice ciclo for per creare non uno ma 10 numeri casuali:
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
int main()
{
// inizializzo il generatore di numeri pseudo-casuali
srand(time(NULL));
for(int i=0;i<10;i++) {
// genero un numero casuale compreso tra 0 e 9
cout<<rand()%10<<endl;
}
}
La funzione srand()
serve a inizializzare la funzione per la generazione dei numeri casuali: senza di essa allo stesso seed (seme) il programma estrarrebbe sempre gli stessi numeri casuali. Un trucco per rendere casuale il seme è quello di impostarlo con time(NULL) o time(0) incorporando la relativa funzione che si trova nella libreria ctime (che abbiamo incluso nel nostro progetto).
Si noti che nel primo esempio proposto in questo articolo la funzione srand()
non era presente: tutte le volte che rand()
viene usato senza che il programmatore abbia premesso srand()
è come se fosse stato impostato srand(1)
… ed è per questo che l’esempio precedente restituiva sempre la stessa sequenza di risultati con conseguenze non ottimali sulla casualità del risultato atteso dal codice.
Superare i limiti di rand() con il metodo std::random
Come abbiamo già avuto modo di notare, l’uso di rand()
per la generazione di numeri random in C++ presenta alcune limitazioni:
- Bassa qualità della casualità –
rand()
non è adatto per applicazioni critiche (es. crittografia) poiché può generare sequenze prevedibili. - Non è thread-safe – in ambienti multi-thread può causare problemi.
- Bias nella distribuzione –
rand() % N
non genera numeri perfettamente uniformi se RAND_MAX + 1 non è un multiplo di N.
Per risolvere questi problemi, si consiglia di usare la libreria <random> introdotta con C++11 che offre migliore qualità e controllo sui numeri generati.
E’ bene precisare che si tratta ancora di numeri pseudo-casuali ma il risultato che è possibile ottenere è ben migliore rispetto a quello visto in precedenza.
Ecco un esempio che utilizza il generatore Mersenne Twister (std::mt19937) per generare un numero casuale tra 1 e 100 in C++:
#include <iostream>
#include <random>
int main() {
// Creazione del generatore di numeri casuali con seed casuale
std::random_device rd;
std::mt19937 gen(rd()); // Generatore Mersenne Twister
std::uniform_int_distribution<int> distribuzione(1, 100); // Intervallo [1, 100]
// Genera un numero casuale
int numero_casuale = distribuzione(gen);
std::cout << "Numero casuale generato: " << numero_casuale << std::endl;
return 0;
}
Ma quali sono i vantaggi di <random>
rispetto a rand()
?
- Migliore casualità: usa algoritmi avanzati come il Mersenne Twister
- Distribuzioni personalizzabili: puoi generare numeri con distribuzione normale, esponenziale, poissoniana, etc.
- Migliore sicurezza: std::random_device può essere usato anche per applicazioni crittografiche.
Generare numeri casuali in virgola mobile
Se vuoi generare numeri casuali con virgola mobile è possibile usare std::uniform_real_distribution<float>
:
#include <iostream>
#include <random>
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> distribuzione(0.0, 1.0);
float numero_casuale = distribuzione(gen);
std::cout << "Numero casuale generato: " << numero_casuale << std::endl;
return 0;
}
Questo codice genererà numeri casuali tra 0.0 e 1.0 con una distribuzione uniforme.
Conclusione
La generazione di numeri casuali è un aspetto fondamentale della programmazione, ma come abbiamo visto, il termine casuale è spesso una semplificazione.
In realtà, rand()
e perfino i generatori avanzati come std::mt19937
producono numeri pseudo-casuali, cioè sequenze deterministiche che sembrano casuali, ma sono generate seguendo algoritmi prevedibili.
Questo è sufficiente per la maggior parte delle applicazioni, come i videogiochi o la simulazione di dati. Tuttavia, quando l’imprevedibilità è cruciale (come nella crittografia, nella sicurezza informatica o nelle simulazioni scientifiche) la pseudo-casualità non basta.
In questi casi, entrano in gioco generatori veramente casuali, che si basano su fenomeni fisici imprevedibili (ad esempio, il rumore termico nei circuiti elettronici o il decadimento radioattivo).
Se il tuo obiettivo è avere un numero sufficientemente casuale per un gioco o una simulazione, std::mt19937
è più che adeguato. Ma se devi garantire una sicurezza assoluta, potresti dover esplorare strumenti più sofisticati come hardware random number generators (HRNG) o servizi esterni per l’entropia casuale.
Alla fine, la vera domanda non è “Come generare un numero casuale?”, ma “Quanto casuale deve essere il mio numero?”. E la risposta dipende sempre dal contesto in cui lo stai usando.