Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
nombres_aleatoires [2015/06/22 02:00] gbdivers |
nombres_aleatoires [2018/08/30 20:35] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ^ [[nombres_reels|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[rvalue_et_lvalue|Chapitre suivant]] ^ | + | ^ [[definir_ses_types|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[collection|Chapitre suivant]] ^ |
- | ====== Les nombres pseudo-aléatoires ====== | + | ====== [Aller plus loin] Les nombres pseudo-aléatoires ====== |
- | http://en.cppreference.com/w/cpp/numeric/random | + | Un nombre aléatoire est un nombre dont la valeur est choisie au hasard. Ils sont utilisés dans de nombreux contextes, par exemple dans les jeux vidéo (pour avoir un comportement non prédictible), en cryptographie (pour encoder des informations), ou dans les tests automatisés de code (pour vérifier le comportement d'un programme avec des valeurs indéterminées). Vous verrez dans différents projets de ce cours des cas d'utilisation des nombres aléatoires. |
- | + | ||
- | Un nombre aléatoire est un nombre dont la valeur est choisit au hasard. Ils sont utilisé dans de nombreux contextes, par exemple dans un jeu vidéo (pour avoir un comportement non prédictible), en cryptographie (pour encoder des informations), dans les tests automatisés du code (pour vérifier le comportement d'un programme avec des valeurs indéterminées). Vous verrez dans différents projets de ce cours des cas d'utilisation des nombres aléatoires. | + | |
===== Qualité des nombres aléatoires ===== | ===== Qualité des nombres aléatoires ===== | ||
- | Tirer des nombres aléatoires peut sembler être une tâche facile. On peut par exemple prendre un dé de jeux (non pipé) à six faces et le lancer plusieurs fois pour obtenir une série de nombres aléatoires compris entre un et six. On peut également lancer une pièce de monnaie pour obtenir des nombres aléatoires valant zéro (Pile) ou un (Face). | + | Tirer des nombres aléatoires peut sembler être une tâche facile. On peut par exemple prendre un dé (non pipé) à six faces et le lancer plusieurs fois pour obtenir une série de nombres aléatoires compris entre un et six. On peut également lancer une pièce de monnaie pour obtenir des nombres aléatoires valant zéro (pile) ou un (face). |
- | La difficulté vient du fait qu'il est facile d'obtenir des suites de nombres dont on n'arrive pas à prédire la prochaine valeur, mais qui présente quand même des propriétés mathématiques prédictibles. Si on additionne par exemple la valeur de deux dés, on aura un nombre aléatoire compris entre 2 et 12. Mais la probabilité d'obtenir un 12 est bien inférieure (1 chance sur 36) à la probabilité d'obtenir un 7 (12 chances sur 36). | + | La difficulté vient du fait qu'il est facile d'obtenir des suites de nombres dont on n'arrive pas à prédire la prochaine valeur mais qui présentent quand même des propriétés mathématiques prédictibles. Si on additionne par exemple la valeur de deux dés, on aura un nombre aléatoire compris entre 2 et 12. Mais la probabilité d'obtenir un 12 est bien inférieure (1 chance sur 36) à la probabilité d'obtenir un 7 (12 chances sur 36). |
- | Quand vous jouez à un jeu de société, ce n'est pas très important que les nombres obtenus ne soient pas parfaitement aléatoire (et c'est de toute façon intégré comme composante du jeu). Mais lorsque les nombres aléatoires permettent de crypter des données importantes ou sécuriser une numéro de carte bancaire, la moindre propriété mathématique prédictible sur une série de nombres aléatoires peut permettre de contourner ces sécurités. | + | Quand vous jouez à un jeu de société, ce n'est pas très important que les nombres obtenus ne soient pas parfaitement aléatoires (et c'est même intégré comme composante du jeu). Mais lorsque les nombres aléatoires permettent de crypter des données importantes ou de sécuriser un numéro de carte bancaire, la moindre propriété mathématique prédictible sur une série de nombres aléatoires peut permettre de contourner ces sécurités. |
- | Il ne donc pas simplement que les nombres semblent aléatoires. Il faut le prouver mathématiquement. (Et bien sûr, cela sort complètement du domaine de ce livre, ces sont des travaux de recherches très pointus). | + | Il ne faut donc pas simplement que les nombres semblent aléatoires. Il faut le prouver mathématiquement. Et bien sûr, cela sort complètement du cadre de ce cours, ce sont des travaux de recherches mathématique très pointus. |
Pour en revenir à la programmation, on peut se poser une question : comment générer des nombres parfaitement aléatoires sur un ordinateur ? Ce n'est pas si simple, puisqu'un ordinateur est prévu pour être parfaitement prédictible (la même suite d'instructions avec les mêmes données produira le même résultat). | Pour en revenir à la programmation, on peut se poser une question : comment générer des nombres parfaitement aléatoires sur un ordinateur ? Ce n'est pas si simple, puisqu'un ordinateur est prévu pour être parfaitement prédictible (la même suite d'instructions avec les mêmes données produira le même résultat). | ||
Ligne 38: | Ligne 36: | ||
* les générateurs de distributions statistiques. | * les générateurs de distributions statistiques. | ||
- | Si vous travaillez sur du code qui a besoin d'un générateur le plus aléatoire possible, il faudra vérifier le fonctionnement du générateur ou utiliser une bibliothèque dédiée à la génération de nombres aléatoires pour la cryptographique (ces générateurs sont plus lents que les générateurs utilisés dans la bibliothèque standard, ce qui explique pourquoi ils ne sont pas utilisés par défaut). | + | Si vous travaillez sur du code qui a besoin d'un générateur le plus aléatoire possible, il faudra vérifier le fonctionnement du générateur ou utiliser une bibliothèque dédiée à la génération de nombres aléatoires pour la cryptographie (ces générateurs sont plus lents que les générateurs utilisés dans la bibliothèque standard, ce qui explique pourquoi ils ne sont pas utilisés par défaut). |
===== Générateurs non déterministes ===== | ===== Générateurs non déterministes ===== | ||
- | L'utilisation d'un générateur aléatoire est relativement simple, même si cela utilise un syntaxe que vous n'avez pas encore vu. Il faut créer une variable utilisant le générateur comme type, puis appeler cette variable comme une fonction. On parle d'"objet appelable" (//callable// en anglais). En pratique, la syntaxe est la suivante : | + | L'utilisation d'un générateur aléatoire est relativement simple, même si cela utilise une syntaxe que vous n'avez pas encore vu. Il faut créer une variable utilisant le générateur comme type, puis appeler cette variable comme une fonction. On parle d'"objet appelable" (//callable// en anglais). En pratique, la syntaxe est la suivante : |
<code cpp> | <code cpp> | ||
Ligne 76: | Ligne 74: | ||
<note>En pratique, l'implémentation des générateurs n'est pas définie dans la norme C++, chaque implémentation de la bibliothèque standard peut avoir un comportement différent. Il n'est donc pas possible de prévoir le comportement réel de ce générateur. | <note>En pratique, l'implémentation des générateurs n'est pas définie dans la norme C++, chaque implémentation de la bibliothèque standard peut avoir un comportement différent. Il n'est donc pas possible de prévoir le comportement réel de ce générateur. | ||
- | Par exemple, sur [[http://coliru.stacked-crooked.com/|Coliru]], à chaque fois que le programme est relancé, il va générer la même suite de nombres aléatoires. Le même code sur [[http://ideone.com/|Ideone]] va produire des nombres aléatoires différents à chaque fois que le programme est lancé.</note> | + | Par exemple, sur [[http://coliru.stacked-crooked.com/|Coliru]], à chaque fois que le programme est relancé, il va générer la même suite de nombres aléatoires. Le même code sur [[http://ideone.com/|Ideone]] va produire des nombres aléatoires différents à chaque fois que le programme est lancé. |
+ | |||
+ | Pour savoir si le générateur non déterministe est effectivement non déterministe ou pas, il faut afficher l'entropie en utilisant la fonction ''entropy'' : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <random> | ||
+ | |||
+ | int main() { | ||
+ | std::random_device rd; | ||
+ | std::cout << "Entropy: " << rd.entropy() << std::endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Si la valeur affichée est nulle, alors le générateur est déterministe. | ||
+ | </note> | ||
Le problème de ce générateur est qu'il n'est pas possible de prouver qu'il a un comportement parfaitement aléatoire, qu'on ne peut prédire aucune caractéristique mathématique sur la série de nombres générés. On préférera donc généralement les générateurs de nombres aléatoires déterministes. | Le problème de ce générateur est qu'il n'est pas possible de prouver qu'il a un comportement parfaitement aléatoire, qu'on ne peut prédire aucune caractéristique mathématique sur la série de nombres générés. On préférera donc généralement les générateurs de nombres aléatoires déterministes. | ||
Ligne 82: | Ligne 95: | ||
===== Générateurs déterministes ===== | ===== Générateurs déterministes ===== | ||
- | Un générateur déterministe produit des nombres aléatoires selon un algorithme complexe donné. Ce type de générateur prend en argument une graine (//seed// en anglais), qui définie la série de nombres aléatoires à générer. Utiliser deux fois la même graine produit la même série de nombres. | + | Un générateur déterministe produit des nombres aléatoires selon un algorithme complexe donné. Ce type de générateur prend en argument une graine (//seed// en anglais), qui définit la série de nombres aléatoires à générer. Utiliser deux fois la même graine produit la même série de nombres. |
- | graine | + | ==== Graine d'un générateur ==== |
- | - c'est quoi ? | + | |
- | - utiliser la graine pour générer plusieurs fois la même série | + | |
- | - choisir une graine aléatoire avec générateur non déterministe | + | |
- | algos | + | Dans de nombreux cas, on ne souhaite pas utiliser une graine en particulier, on voudra avoir une série différente de nombres aléatoires, à chaque fois qu'on lance une application. Par exemple : |
- | - cf la doc | + | |
- | - 3 algos de base | + | |
- | - 9 générateurs, avec algo de base et valeurs (ie 1 algo de base avec n'importe quelle valeur ne produira pas forcement un générateur parfaitement aléatoire) | + | |
- | sont-il sur ? | + | * dans un jeu, pour que les monstres aient un comportement différent à chaque partie, sans que l'on puisse prédire ce qu'ils vont faire ; |
- | - pourquoi ne pas redéfinir la graine | + | * dans un programme testant la qualité d'un code (test unitaire), pour avoir des valeurs qui changent à chaque fois que l'on relance les tests ; |
- | - peut on retrouver la graine si on une série de nombres ? Probabilité très faible | + | * dans un programme d'encodage de données, pour que l'on ne puisse pas prédire l'encodage utilisé. |
- | - Peut on retrouver la suite si on connait le début ? Encore moins probable | + | |
- | - importance des "bonnes" pratiques, pour éviter que les nombres aléatoires deviennent prédictibles (exemple de enigma) | + | |
- | Astuce : pour les tests, afficher la graine aléatoire, pour pouvoir reproduire. | + | Mais dans certains cas, il sera intéressant d'utiliser une graine connue. Un exemple classique est un programme de test de code, qui détecte une erreur en utilisant une série spécifique de nombres aléatoires. Pour reproduire l'erreur, on pourra générer la même série de nombres en utilisant la même graine. |
+ | Pour créer un générateur en utilisant une graine, il suffit de mettre cette graine comme argument lors de la création du générateur. (Note : il existe plusieurs générateurs déterministes, qui seront vu par la suite. Pour le début de ce cours, nous allons utiliser le générateur ''std::default_random_engine''). | ||
<code cpp> | <code cpp> | ||
+ | std::default_random_engine engin { seed }; | ||
+ | </code> | ||
+ | |||
+ | Ce code créé une variable nommée ''engin'', de type ''std::default_random_engine'' et initialisé avec la valeur ''seed''. | ||
+ | |||
+ | Pour générer les nombres aléatoires, il faut ensuite appeler la variable ''engin'' comme une fonction, comme vous avez fait pour ''random_device'' : | ||
+ | |||
+ | <code cpp main.cpp> | ||
#include <iostream> | #include <iostream> | ||
#include <random> | #include <random> | ||
+ | |||
+ | int main() { | ||
+ | std::default_random_engine engin { 123 }; // création du générateur | ||
+ | |||
+ | std::cout << engin() << std::endl; // génération d'un nombre aléatoire | ||
+ | std::cout << engin() << std::endl; // génération d'un nombre aléatoire | ||
+ | std::cout << engin() << std::endl; // génération d'un nombre aléatoire | ||
+ | } | ||
+ | </code> | ||
+ | affichera : | ||
+ | |||
+ | <code> | ||
+ | 2067261 | ||
+ | 384717275 | ||
+ | 2017463455 | ||
+ | </code> | ||
+ | |||
+ | Remarque : cette série de nombres est déterministe, ce qui veut dire que si vous utiliser le même générateur et la même graine que dans le code, vous devriez avoir exactement la même série de nombres aléatoires (contrairement au ''random_device'', pour lequel vous pouvez avoir un résultat différent que celui donné dans le cours). | ||
+ | |||
+ | Pour générer une série en utilisant une graine qui change a chaque fois que le programme est lancé, il est classique d'initialiser le générateur avec le temps (ce qui garantit que la graine change à chaque fois). Par exemple : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <random> | ||
+ | #include <ctime> | ||
+ | |||
int main() { | int main() { | ||
- | std::default_random_engine engin0(0); | + | auto const seed = std::time(nullptr); |
- | std::cout << engin0() << std::endl; | + | std::cout << seed << std::endl; |
- | std::cout << engin0() << std::endl; | + | std::default_random_engine engin { seed }; |
- | std::cout << engin0() << std::endl; | + | } |
- | std::cout << std::endl; | + | </code> |
- | + | ||
- | std::default_random_engine engin1(1000); | + | La fonction ''std::time'' retourne le temps passé depuis une date fixée (généralement le 1er janvier 1970), en secondes. |
- | std::cout << engin1() << std::endl; | + | |
- | std::cout << engin1() << std::endl; | + | <note> |
- | std::cout << engin1() << std::endl; | + | Encore une fois, on peut remarquer des "failles" dans le système de génération des nombres aléatoires. Du fait que la graine change toutes les secondes, cela veut dire que si on génère deux graines trop rapidement, elles seront identiques. |
- | std::cout << std::endl; | + | |
+ | De même, si on sait à peut près quand un nombre aléatoire a été généré, on peut retrouver plus facilement la graine qui a été utilisée et la série de nombres aléatoires. | ||
+ | |||
+ | Dans un programme critique, par exemple en programme d'encodage de données importantes, cela constitue des failles de sécurité. On voit ici qu'il est finalement assez facile, si on ne fait pas attention, de rendre prédictible un générateur et donc de créer des failles de sécurité dans un programme. | ||
+ | |||
+ | **L'utilisation d'un générateur aléatoire de qualité n'est pas suffisant, il faut aussi l'utiliser correctement. | ||
+ | ** | ||
+ | |||
+ | Pour des applications qui nécessite un niveau de sécurité élevé, on utilisera à la place des générateurs dédiés à la cryptographie.</note> | ||
+ | |||
+ | ==== Les différents générateurs ==== | ||
+ | |||
+ | Plusieurs générateurs déterministes sont proposés dans la bibliothèque standard du C++. Ces générateurs sont définies par l’algorithme utilisé et par les valeurs utilisées pour le configurer. Pour comprendre cela, regardons un peu plus en détail la documentation : [[http://en.cppreference.com/w/cpp/numeric/random|Pseudo-random number generation]]. | ||
+ | |||
+ | Trois algorithmes de génération sont utilisé. La documentation donne des liens vers les pages de Wikipédia décrivant ces algorithmes. | ||
+ | |||
+ | * ''std::linear_congruential_engine'' : algorithme congruentiel linéaire ; | ||
+ | * ''std::mersenne_twister_engine'' : algorithme de Mersenne Twister ; | ||
+ | * ''std::subtract_with_carry_engine'' : algorithme de soustraction avec retenue. | ||
+ | |||
+ | <note>Il est très probable que vous ne connaissiez pas ces algorithmes... et cela ne va pas changer tout de suite :) Le but de ce cours est de vous expliquer comment utiliser les générateurs aléatoires et lire la documentation, pas d'expliquer chaque algorithme en détail. Pour cela, il vous faudra lire les documentations, voire lire un cours de cryptographie.</note> | ||
+ | |||
+ | Il existe ensuite trois adaptateurs, qui permettent de modifier un autre algorithme. Les adaptateurs de la bibliothèques standard sont : | ||
+ | |||
+ | * ''std::discard_block_engine'' ; | ||
+ | * ''std::independent_bits_engine'' ; | ||
+ | * ''std::shuffle_order_engine''. | ||
+ | |||
+ | Et pour terminer, les différents générateurs proposés par défaut, à partir de ces algorithmes et adaptateurs : | ||
+ | |||
+ | * ''std::minstd_rand0'' ; | ||
+ | * ''std::minstd_rand'' ; | ||
+ | * ''std::mt19937'' ; | ||
+ | * ''std::mt19937_64'' ; | ||
+ | * ''std::ranlux24_base'' ; | ||
+ | * ''std::ranlux48_base'' ; | ||
+ | * ''std::ranlux24'' ; | ||
+ | * ''std::ranlux48'' ; | ||
+ | * ''std::knuth_b''. | ||
+ | |||
+ | Ces générateurs s'utilisent de la même façon que le générateur ''std::default_random_engine'' cité précédemment. Par exemple, pour ''std::ranlux48_base'', avec une graine fixe : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <random> | ||
+ | |||
+ | int main() { | ||
+ | std::ranlux48_base engin { 123 }; // création du générateur | ||
- | std::default_random_engine engin2(0); | + | std::cout << engin() << std::endl; // génération d'un nombre aléatoire |
- | std::cout << engin2() << std::endl; | + | std::cout << engin() << std::endl; // génération d'un nombre aléatoire |
- | std::cout << engin2() << std::endl; | + | std::cout << engin() << std::endl; // génération d'un nombre aléatoire |
- | std::cout << engin2() << std::endl; | + | |
- | std::cout << std::endl; | + | |
} | } | ||
</code> | </code> | ||
- | ===== générateurs de distributions statistiques ===== | + | affiche : |
- | Générateurs précédents : chaque nombre à même chance d'etre générer. Mais souvent besoin de nombres aléatoires qui suivent une loi particulière. Par exemple, somme de deux dés 6 non equiprobable, mais suis loi particulière. Générateur distributions permettent de générer ces lois | + | <code> |
+ | 52617863149155 | ||
+ | 181669238551779 | ||
+ | 228994550894105 | ||
+ | </code> | ||
- | HS sujet, voir cours de stat. Quelques lois importantes : | + | **Comment générer un nombre aléatoire en pratique ?** |
- | - uniforme entre min et max : équiprobable, dans un range. Version int et real | + | |
- | - binomiale : pile ou face | + | |
- | - loi normale | + | |
- | ===== En pratique ===== | + | Au final, cela fait beaucoup d'algorithmes à utiliser, vous ne savez probablement pas quel algorithme utiliser parmi tous les générateurs proposés. La réponse est simple : |
- | Le plus souvent, loi uniforme, générateur par défaut, graine aléatoire. | + | * si cela n'a pas d'importance, uniquement ''std::default_random_engine'' ; |
+ | * si c'est important : uniquement les algorithmes que vous avez étudié et que vous comprenez. | ||
+ | |||
+ | ===== Générateurs de distributions statistiques ===== | ||
+ | |||
+ | ==== Rappel sur les distributions statistiques ==== | ||
+ | |||
+ | Dans les générateurs précédents, chaque valeur possible a la même chance d'apparaître (équiprobable). Dans de nombreuses situations (probablement dans la majorité des situations), on a besoin de restreindre le domaine de définition des nombres générés (par exemple, générer des nombres entre 0 et 10) ou d'avoir des probabilités différentes pour chaque valeur (par exemple, pour des nombres compris entre 0 et 1, plus une valeur est proche de 0, plus elle aura de chance d'apparaître). | ||
+ | |||
+ | Par exemple, si vous avez un jeu de 32 cartes et que vous souhaitez en tirer une au hasard, il faudra générer un nombre compris en 1 et 32. Si vous avez un personnage dans un jeu vidéo contrôlé aléatoirement, qui ne peut se déplacer que dans quatre directions, vous devrez générer un nombre aléatoire qui ne peut prendre par exemple que les valeurs 0 (monter), 1 (descendre), 2 (aller à droite) ou 3 (aller à gauche). Ce type de distribution est uniforme, chaque valeur à la même probabilité d'apparaître. | ||
+ | |||
+ | D'autres exemples. Si vous créer un jeu de simulation de courses de voiture et que le joueur tourne de 10°, dans le monde réel, ça ne sera pas exactement 10°, mais 10° plus ou moins une erreur (due à l’imprécision du volant, des imperfections de la route, etc). Dans le jeu, pour avoir plus de réalisme, on peut modifier l'angle avec une erreur, pour obtenir par exemple 10.2° ou 9.9°. On pourra avoir des valeurs très différentes de 10°, par exemple 12° ou 8.5°, mais cela sera beaucoup plus rare. Pour simuler cela, on va utiliser une distribution normale. | ||
+ | |||
+ | ==== Les générateurs standard de distribution ==== | ||
+ | |||
+ | La bibliothèque standard du C++ contient d'autres distributions que les distributions uniformes et normale. Vous pouvez avoir le détail dans la [[http://en.cppreference.com/w/cpp/numeric/random|documentation]] pour les fonctions à utiliser et sur [[https://fr.wikipedia.org/wiki/Liste_de_lois_de_probabilit%C3%A9|Wikipédia]] pour les explications sur les propriétés des distributions. Dans la majorité des cas, les deux distributions citées seront suffisantes, nous verrons cela dans la suite du cours. | ||
+ | |||
+ | Il existe deux distributions uniformes et une normale : | ||
+ | |||
+ | * ''std::uniform_int_distribution'' pour générer des nombres entiers selon une loi uniforme ; | ||
+ | * ''std::uniform_real_distribution'' pour générer des nombres réels selon une loi uniforme ; | ||
+ | * ''std::normal_distribution'' pour générer des nombres réels selon une loi normale. | ||
+ | |||
+ | Ces générateurs ne sont pas à proprement parlé des générateurs de nombres aléatoires. Ils prennent en fait l'un des générateurs aléatoires vu précédemment pour générer un nombre respectant une loi donnée. Ce ne sont pas non plus des adaptateurs, comme ceux vu précédemment, puisqu'ils ne modifient pas le comportement des générateurs aléatoires. Ces générateurs de distribution prennent simplement un générateur en paramètre et l'utilise en interne pour générer des nombres. | ||
+ | |||
+ | ==== Initialisation ==== | ||
+ | |||
+ | La création d'un générateur de distribution se fait en deux étapes : | ||
+ | |||
+ | * l'initialisation, permet de définir les paramètres de la distribution ; | ||
+ | * la génération, en utilisant un générateur aléatoire. | ||
+ | |||
+ | Pour initialiser un générateur de distribution, il faut créer une variable en utilisant le type de générateur que vous souhaitez, et en donnant les paramètres de la distribution. Ces paramètres varient selon le type de générateur : | ||
+ | |||
+ | * valeurs minimale et maximale pour les générateurs de loi uniforme ; | ||
+ | * moyenne et déviation standard pour le générateur de loi normale. | ||
+ | |||
+ | Le code C++ correspond est relativement similaire à ce que vous avez déjà vu. | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <random> | ||
+ | #include <iostream> | ||
+ | |||
+ | int main() { | ||
+ | std::uniform_int_distribution<> uniform_int(0, 10); // loi uniforme sur des entiers | ||
+ | std::uniform_real_distribution<> uniform_real(-1, 1); // loi uniforme sur des réels | ||
+ | std::normal_distribution<> normal(5, 1); // loi normale | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Vous avez déjà rencontré la notation avec chevrons ''<'' et ''>'' dans ce cours. Dans les codes précédents, un paramètre était placé entre ces chevrons, mais ce n'est pas une obligation ici. Si les chevrons ne contiennent rien, cela signifie que le paramètre par défaut est utilisé (voir la documentation pour connaître les paramètres par défaut. Ici, les paramètres par défaut sont ''int'' pour la loi uniforme sur les entiers et ''double'' pour les deux autres lois). Vous pouvez utiliser un autre paramètre : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <random> | ||
+ | #include <iostream> | ||
+ | |||
+ | int main() { | ||
+ | std::normal_distribution<> double_normal(5, 1); // loi normale utilisant des double | ||
+ | std::normal_distribution<float> float_normal(5, 1); // loi normale utilisant des float | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Pour rappel, ''double'' est un type de nombres réels double précision (généralement 64 bits) et ''float'' est un type de nombres réels simple précision (généralement 32 bits). | ||
+ | |||
+ | ==== Note importante sur les types ==== | ||
+ | |||
+ | Il faut bien faire la différence entre les différents types d'arguments utilisés pour initialiser une variable de type générateur de distribution. | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | std::normal_distribution<float> normal(5, 1); | ||
+ | </code> | ||
+ | |||
+ | Le type ''float'' permet de préciser le type ''std::normal_distribution''. Les nombres générés seront de type ''float'', cette information est prise en compte lors de la compilation : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | std::normal_distribution<float> normal(5, 1); | ||
+ | const std::string value { normal(engin) }; // erreur, impossible de convertir un float en std::string | ||
+ | </code> | ||
+ | |||
+ | Le type passé en paramètre fait partie intégrante du type final. Le type de ''normal'' n'est pas ''std::normal_distribution'', mais bien ''std::normal_distribution<float>''. Cela signifie par exemple que les types ''std::normal_distribution<>'' (dont le type par défaut est ''double'') et ''std::normal_distribution<float>'' ne sont pas compatibles entre eux : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | std::normal_distribution<float> d1(5, 1); | ||
+ | std::normal_distribution<float> d2 { d1 }; // ok, d1 et d2 sont de même type | ||
+ | std::normal_distribution<double> d3 { d1 }; // erreur, d1 et d3 ne sont pas compatibles | ||
+ | </code> | ||
+ | |||
+ | Les valeurs 5 et 1 correspondent aux paramètres de la distribution. Elles ne sont pas utilisées lors de la compilation, mais uniquement lors de l'exécution. | ||
+ | |||
+ | <note>La séparation de la phase de compilation (//compile-time//) et de la phase d'exécution (//runtime//) est une notion importante en C++. Il est nécessaire de bien comprendre ce qui est fait à chaque étape. Vous verrez cette notion régulièrement.</note> | ||
+ | |||
+ | ==== Génération ==== | ||
+ | |||
+ | La dernière étape est la génération d'un nombre aléatoire selon une distribution. Les générateurs de distribution sont des objets-fonctions, comme les générateurs aléatoires. Pour rappel, cela signifie que l'on peut appeler la variable comme une fonction : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | std::normal_distribution<float> normal(0, 1); // initialisation | ||
+ | auto const value = normal(engin); // génération | ||
+ | </code> | ||
+ | |||
+ | Contrairement aux générateurs aléatoires qui ne prennent pas d'argument, les générateurs de distribution prennent en argument le générateur aléatoire à utiliser. À chaque fois que le générateur de distribution est appelé, il appelle en interne le générateur aléatoire pour générer un nombre aléatoire, puis l'adapte en fonction de la distribution. | ||
+ | |||
+ | Pour résumer l'utilisation des générateurs de distributions, ceux-ci utilisent trois sources d'information pour générer un nombre : | ||
+ | |||
+ | * le type de valeur générée (le type passé en argument entre chevrons, qui sera le même type que la variable ''value'' avec ''auto'') ; | ||
+ | * le type de distribution et ses paramètres (définis lors de l'initialisation du générateur de distribution) ; | ||
+ | * le générateur aléatoire utilisé (défini lors de la génération des nombres). | ||
+ | |||
+ | ===== Pour résumer ===== | ||
+ | |||
+ | Au final, vous devriez avoir un code similaire à cela pour générer un nombre aléatoire (à adapter selon le type de distribution) : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <random> | ||
+ | #include <ctime> | ||
+ | |||
+ | int main() { | ||
+ | auto const seed = std::time(nullptr); // génération de la graine | ||
+ | std::default_random_engine engin { seed }; // générateur aléatoire | ||
+ | std::normal_distribution<float> normal(0, 1); // générateur de distribution | ||
+ | std::cout << normal(engin) << std::endl; // génération | ||
+ | } | ||
+ | </code> | ||
===== Travaux pratiques ===== | ===== Travaux pratiques ===== | ||
- | Donner une série de nombres aléatoires, produit avec un algo (donné ou non), une graine comprise entre 0 et 100. Essayer de retrouver la graine/algo | + | 1. Donner une série de nombres aléatoires, produit avec un algo/graine (donné ou non), une graine comprise entre 0 et 100. Essayer de retrouver la graine/algo __ probablement à faire plus tard, après les boucles et tests__ |
+ | |||
+ | 2. __exos : proposer des exos avec fonctions de calculs et affiche déjà écrit__ | ||
- | ^ [[nombres_reels|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[rvalue_et_lvalue|Chapitre suivant]] ^ | + | ^ [[definir_ses_types|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[collection|Chapitre suivant]] ^ |
- | {{tag> Cours C++}} |