Gestion des nombres aléatoires avec la bibliothèque standard C++

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 et std::uniform_real_distribution<double> pour les flottants.
  • Bernoulli : Utile pour les tests binaires (vrai/faux) comme std::bernoulli_distribution ou std::binomial_distribution.
  • Normale : std::normal_distribution pour les courbes de Gauss, ainsi que les distributions de Cauchy, Fisher ou Student.
  • Temporelle/Fréquence : std::poisson_distribution ou std::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.

Étiquettes: cpp STL Random programming-languages software-development

Publié le 6 juin à 06h14