Depuis la norme C++11, le langage propose une interface robuste et flexible pour la génération de nombres aléatoires via l'en-tête <random>. Cette approche sépare la source de l'entropie (le moteur) de la mise en forme des données (la distribution).
Le générateur non-déterministe : random_device
L'unité std::random_device agit comme une source d'entropie matérielle. Sur les systèmes de type Unix (comme Linux ou macOS), elle sollicite généralement /dev/urandom, tandis que sur Windows, elle utilise des API système comme rand_s.
Elle est principalement utilisée pour initialiser ("seeder") les moteurs de calcul pseudo-aléatoires, car son coût de génération peut être élevé par rapport à un algorithem logiciel.
Moteurs de génération (Engines)
Les moteurs sont des algorithmes qui produisent une séquence de nombres bruts. La bibliothèque standard propose plusieurs implémentations :
- std::default_random_engine : Un choix par défaut dépendant de l'implémentasion du compilateur.
- std::mt19937 : Algorithme Mersenne Twister 32 bits, offrant une très longue période et une excellente qualité statistique.
- std::mt19937_64 : Variante 64 bits du Mersenne Twister.
- std::linear_congruential_engine : Moteur à congruence linéaire, plus rapide mais souvent de moindre qualité.
Pour initialiser un moteur, on utilise soit une valeur fixe (pour la reproductibilité), soit une valeur changeante comme l'horloge système :
std::default_random_engine moteur(static_cast<unsigned int>(std::time(nullptr)));
Distributions statistiques
Les distributions transforment le flux de nombres bruts du moteur en une plage et une forme statistique précises :
- Uniforme :
std::uniform_int_distribution<int>pour les entiers etstd::uniform_real_distribution<double>pour les flottants. - Bernoulli : Utile pour les tests binaires (vrai/faux) comme
std::bernoulli_distributionoustd::binomial_distribution. - Normale :
std::normal_distributionpour les courbes de Gauss, ainsi que les distributions de Cauchy, Fisher ou Student. - Temporelle/Fréquence :
std::poisson_distributionoustd::exponential_distribution.
Gestion de la persistance de l'état
Une erreur fréquente consiste à instancier le moteur et la distribution localement dans une fonction appelée fréquemment. Cela réinitialise le générateur à chaque appel, produisant souvent la même valeur. Il est préférable d'utiliser le mot-clé static.
#include <iostream>
#include <vector>
#include <random>
// Approche recommandée avec état statique
std::vector<int> produireSequence(int taille) {
static std::random_device rd;
static std::mt19937 generateur(rd());
static std::uniform_int_distribution<int> plage(1, 100);
std::vector<int> resultat;
for(int i = 0; i < taille; ++i) {
resultat.push_back(plage(generateur));
}
return resultat;
}
Exemple complet d'implémentation
Voici comment combiner un moteur, une graine temporelle et une distribution uniforme pour simuler un lancer de dés :
#include <iostream>
#include <random>
#include <ctime>
#include <functional>
int main() {
// Initialisation avec le temps système
std::default_random_engine moteur(static_cast<unsigned int>(std::time(0)));
// Définition d'une plage entre 1 et 6 inclus
std::uniform_int_distribution<int> de6(1, 6);
// Utilisation de std::bind pour créer un générateur prêt à l'emploi
auto lancerDe = std::bind(de6, moteur);
std::cout << "Lancers : ";
for(int n = 0; n < 5; ++n) {
std::cout << lancerDe() << " ";
}
return 0;
}
Note : Pour initialiser des distributions au sein d'une classe, il est conseillé de passer par la liste d'initialisation du constructeur afin de configurer correctement les bornes dès l'instanciation de l'objet.