C'est un vaste sujet...
Mais pour le principe, les suites pseudo-aléatoires sont des suites de nombres calculées en tant que suites mathématiques comme vous connaissez (p.ex. u_n = 3*u_{n-1} + 5*u_{n-2}+7) mais telles que :
- la distribution des valeurs obtenues (c.-à-d. combien de fois chaque valeur apparait) est aussi proche que possible de la distribution voulue (typiquement uniforme : que chaque valeur possible apparaisse asymptotiquement autant que n'importe quelle autre valeur possible)
- que les dépendances entre valeurs successives (les probabilités conditionnelles en termes techniques) soient aussi faibles que possible : par exemple que si u_n vaut 10 on ne sache pas qu'à 80% on a la valeur 42, mais que toute valeur possible soit aussi probable que les autres.
Une façon de s'approcher de ces objectifs est d'utiliser des suites sous congruence ; p.ex. si je reprend mon exemple précédent : u_n = 3*u_{n-1} + 5*u_{n-2}+7 mod 192. Évidemment il s'agit de calculer en théorie la forme de la formule et les valeurs des coefficients qui vont bien pour s'approcher des propriétés mentionnées ci-dessus.
De telles suites, calculées de proche en proche, ont donc des propriétés statistiques qui s'approchent du « hasard » (lequel bien sûr n'est (1) qu'une notion asymptotique [aucune suite finie ne peut être qualifiée de « hasard »] et (2) n'est, par définition, pas calculable)
Les suites pseudo-aléatoires dépendent donc aussi de la valeur initiale (u_0), que l'on appelle la graine : avec la même graine on a la même suite pseudo-aléatoire. C'est un avantage ou un inconvénient suivant ce que l'on souhaite faire :
- c'est un inconvénient si l'on souhaite avoir vraiment des comportements différents à chaque nouveau lancement du programme (voir ci-dessous)
- mais c'est un avantage (auquel on ne pense pas spontanément) lorsque l'on veut debuger un programme contant de l'aléatoire : on isole la suite ayant produit le bug et on la reproduit (= choix de la même graine) tant que l'on n'a pas trouvé et corrigé l'erreur.
Pour avoir un comportement différent à chaque run du programme, on cherche donc à partir d'une graine différente à chaque fois. Une façon classique, mais pas si aléatoire que ça [elle va bien pour des exercices, des simulation peu critiques, mais ne va pas du tout pour des applications cryptographiques] consiste à choisir cette valeur (la graine) sur l'horloge. Attention cependant cela gènèrera les mêmes suites pseudo-aléatoires si l'on lance le programme « en même temps » : soit plusieurs fois de suite dans la même seconde par exemple, soit plusieurs instances en parallèle (multi-thread) : ce qui ne produira donc aucune variation entre ces différents runs...
Pour cela, certaines machines ont un générateur « vraiment » aléatoire : lié à des évènements physiques de la machine tels que la température (les dernières décimales) du processeur, les derniers micro-mouvements de la souris etc...
Ce générateur, qui n'existe pas toujours, est justement le random_device de C++ (
https://en.cppreference.com/w/cpp/numeric/random/random_device) et est un bon composant pour initialiser la graine (attention cependant au multi-threading, vérifier s'il est « thread-safe » sinon ajouter des informations propres au thread lui-même. Bref...)
Voilà un peu la « big picture ». Pour en savoir plus :
- sur les suites pseudo-aléatoires en général :
- sur ceux implémentés en C++ :