La question est bien trop vaste pour qu'on puisse y répondre complètement, mais permettez-moi de sélectionner quelques points intéressants :
Pourquoi "également probable"
Supposons que vous disposiez d'un générateur de nombres aléatoires simple qui génère les nombres 0, 1, ..., 10, chacun avec une probabilité égale (pensez à cela comme au classique rand()
). Vous souhaitez maintenant obtenir un nombre aléatoire compris dans l'intervalle 0, 1, 2, chacun avec une probabilité égale. Votre réaction instinctive serait de prendre rand() % 3
. Mais attendez, les restes 0 et 1 se produisent plus souvent que le reste 2, donc ce n'est pas correct !
C'est pourquoi nous avons besoin de distributions qui prennent une source d'entiers aléatoires uniformes et les transforment en la distribution souhaitée, comme par exemple Uniform[0,2]
dans l'exemple. Il est préférable de confier cette tâche à une bonne bibliothèque !
Moteurs
Ainsi, au cœur de tout caractère aléatoire se trouve un bon générateur de nombres pseudo-aléatoires qui génère une séquence de nombres uniformément répartis sur un certain intervalle, et qui ont idéalement une très longue période. L'implémentation standard de rand()
n'est pas souvent le meilleur, et il est donc bon d'avoir le choix. La méthode linéaire-congruentielle et le tordeur de Mersenne sont deux bons choix (LG est en fait souvent utilisé par rand()
) ; là encore, il est préférable de laisser la bibliothèque s'en charger.
Comment cela fonctionne
Facile : d'abord, configurer un moteur et l'ensemencer. Le germe détermine entièrement la séquence entière de nombres "aléatoires", donc a) utilisez-en un autre (par exemple, tiré de /dev/urandom
) à chaque fois, et b) stocker la graine si vous souhaitez recréer une séquence de choix aléatoires.
#include <random>
typedef std::mt19937 MyRNG; // the Mersenne Twister with a popular choice of parameters
uint32_t seed_val; // populate somehow
MyRNG rng; // e.g. keep one global instance (per thread)
void initialize()
{
rng.seed(seed_val);
}
Maintenant, nous pouvons créer des distributions :
std::uniform_int_distribution<uint32_t> uint_dist; // by default range [0, MAX]
std::uniform_int_distribution<uint32_t> uint_dist10(0,10); // range [0,10]
std::normal_distribution<double> normal_dist(mean, stddeviation); // N(mean, stddeviation)
...Et utiliser le moteur pour créer des nombres aléatoires !
while (true)
{
std::cout << uint_dist(rng) << " "
<< uint_dist10(rng) << " "
<< normal_dist(rng) << std::endl;
}
Concurrence
Une raison supplémentaire de préférer <random>
par rapport au traditionnel rand()
est qu'il est maintenant très clair et évident de rendre la génération de nombres aléatoires threadsafe : Il faut soit fournir à chaque thread son propre moteur, local au thread, avec une graine locale au thread, soit synchroniser l'accès à l'objet moteur.
Divers
- Un site article intéressant sur TR1 aléatoire sur codeguru.
-
Wikipedia a un bon résumé (merci, @Justin).
- En principe, chaque moteur devrait typedef un
result_type
qui est le type d'intégrale correct à utiliser pour la graine. Je pense que j'ai eu une fois une implémentation boguée qui m'a forcé à forcer la graine de std::mt19937
à uint32_t
sur x64, éventuellement cela devrait être corrigé et vous pourrez dire MyRNG::result_type seed_val
et rendre ainsi le moteur très facilement remplaçable.