Les nombres aléatoires du C viennent de deux fonctions de <stdlib.h> : rand(), qui produit la valeur suivante, et srand(), qui fixe le point de départ. Ils ne sont pas vraiment aléatoires - c'est une séquence pseudo-aléatoire, calculée de façon déterministe à partir d'une graine - ce qui est une limitation pour la cryptographie et une fonctionnalité pour les tests.
rand() et RAND_MAX
rand() renvoie un int situé entre 0 et RAND_MAX, inclus. RAND_MAX est une macro garantie d'être au moins 32767 ; sous Linux et macOS, elle vaut 2147483647.
Lancez cela deux fois. Les nombres sont identiques les deux fois - et ce n'est pas un bug.
L'initialisation avec srand
Sans appel à srand, la séquence se comporte comme si vous aviez appelé srand(1). Même graine, même séquence, à chaque exécution. Pour obtenir des nombres différents à chaque fois, initialisez avec quelque chose qui change - conventionnellement l'heure courante :
time(NULL) de <time.h> renvoie les secondes écoulées depuis le début de 1970, donc chaque exécution obtient une graine différente. Le cast en unsigned int fait taire un avertissement sur la réduction de time_t.
Trois règles sur l'initialisation, que tout le monde rate :
Initialisez exactement une fois, au début de main. Appeler srand avant chaque rand() est l'anti-motif classique - dans une boucle qui se termine en moins d'une seconde, time(NULL) renvoie la même valeur à chaque itération, donc vous réinitialisez avec le même nombre et rand() renvoie la même première valeur à chaque fois. La sortie est une colonne de « nombres aléatoires » identiques.
Ne réinitialisez pas pour « améliorer » l'aléa. La qualité du générateur vient de l'avancée de son état interne ; réinitialiser cet état jette la séquence.
time(NULL) a une résolution d'une seconde. Deux programmes lancés dans la même seconde obtiennent la même séquence. C'est acceptable pour un jeu et faux pour tout ce où l'indépendance compte.
Un nombre dans un intervalle
L'idiome standard utilise l'opérateur de reste :
rand() % n /* 0 a n-1 */
rand() % n + min /* min a min+n-1 */
Pour obtenir min à max inclus, le nombre de valeurs possibles est max - min + 1 :
Le + 1 est l'endroit où vivent les erreurs de décalage d'un. rand() % 6 donne 0 à 5, donc un lancer de dé est rand() % 6 + 1. Écrire rand() % 7 + 1 pour « inclure 6 » vous donne un dé à sept faces.
La note honnête sur le biais du modulo
rand() % n n'est pas parfaitement uniforme sauf si n divise RAND_MAX + 1 exactement.
Pensez-y avec de petits nombres. Si RAND_MAX valait 9 - donc rand() renvoie 0 à 9, dix valeurs également probables - alors rand() % 3 envoie 0, 3, 6, 9 sur 0 ; 1, 4, 7 sur 1 ; et 2, 5, 8 sur 2. Le résultat 0 se produit de quatre façons sur dix, les résultats 1 et 2 de trois façons chacun. Zéro est 33 % plus probable.
Le même écart existe avec le vrai RAND_MAX, en bien plus petit : les valeurs restantes sont les (RAND_MAX + 1) % n premiers résultats, chacun gagnant une chance supplémentaire sur environ 2,1 milliards. Pour un lancer de dé, un paquet mélangé ou une simulation, c'est immesurable - utilisez % et passez à autre chose.
Quand cela compte - travail statistique, tout ce qui touche à la sécurité - rejetez les valeurs restantes plutôt que de les replier :
La boucle jette la petite plage de valeurs qui causerait l'écart et retire. Elle se termine vite - la tranche rejetée est une fraction infime du tout.
Pour un aléa réellement sensible à la sécurité, rand() est le mauvais outil quel que soit le soin apporté : utilisez arc4random_buf sur macOS et BSD, getrandom() sous Linux, ou BCryptGenRandom sous Windows.
Les doubles aléatoires
Divisez par RAND_MAX pour atterrir dans [0.0, 1.0], puis mettez à l'échelle :
Le cast dans (double) rand() est essentiel. Sans lui, rand() / RAND_MAX est une division entière qui vaut 0 presque toujours, 1 avec une chance sur deux milliards de toucher le maximum - un bug qui ressemble à « tous mes doubles aléatoires valent zéro ». Voir transtypage pour comprendre.
Les séquences reproductibles
Une graine fixe donne la séquence identique à chaque exécution, ce qui est exactement ce que vous voulez pour un test, une session de débogage, ou un jeu avec des codes de niveau partageables :
La graine 42 produit les mêmes cinq nombres chaque fois qu'elle est utilisée, dans cette exécution et dans toute autre avec la même bibliothèque. Cette reproductibilité explique pourquoi une simulation devrait laisser choisir la graine : lancer avec l'horloge normalement, passer une graine fixe pour reproduire un bug.
Une réserve : la séquence pour une graine donnée n'est pas portable. Des bibliothèques C différentes utilisent des générateurs différents, donc la graine 42 sur glibc et la graine 42 sous Windows donnent des nombres différents. Reproductible sur une machine, pas entre machines.
Un jeu de dés
Tout réuni - une seule initialisation, une fonction auxiliaire pour l'intervalle, et un tableau qui comptabilise les résultats :
L'histogramme devrait culminer à 7 et s'amincir vers 2 et 12 - il y a six façons de faire 7 et une seule de faire 2 ou 12. Un générateur produisant ici une distribution plate serait cassé.
Deux pages voisines : la bibliothèque standard cartographie le reste de <stdlib.h>, et les fonctions mathématiques couvrent <math.h>, dont vous aurez besoin dès que des valeurs aléatoires alimenteront de vrais calculs.
Questions fréquentes
Comment générer un nombre aléatoire en C ?
Incluez <stdlib.h>, initialisez une fois au début de main avec srand((unsigned) time(NULL)) (qui demande <time.h>), puis appelez rand() pour chaque valeur. rand() renvoie un int entre 0 et RAND_MAX inclus.
Comment obtenir un nombre aléatoire entre deux valeurs en C ?
Utilisez rand() % (max - min + 1) + min. Pour un lancer de dé entre 1 et 6, c'est rand() % 6 + 1. Le % n ramène le résultat dans 0..n-1 et ajouter min décale la fenêtre - assurez-vous simplement que le compte inclut les deux bornes, ce que fait le + 1.
Pourquoi mon programme C affiche-t-il les mêmes nombres aléatoires à chaque fois ?
Parce que vous n'avez jamais appelé srand. Sans graine, rand() se comporte comme s'il était initialisé avec 1, donc chaque exécution produit la séquence identique. Appelez srand((unsigned) time(NULL)) une fois au démarrage - une fois, pas avant chaque appel à rand(), ce qui empirerait les choses.
Qu'est-ce que le biais du modulo dans la génération aléatoire ?
rand() % n n'est parfaitement uniforme que si n divise RAND_MAX + 1 exactement. Sinon, les premières valeurs apparaissent une fois de plus sur toute la plage, les rendant très légèrement plus probables. Avec RAND_MAX à 2147483647 et un petit n, l'écart est bien en dessous de ce qu'un jeu ou une simulation remarque, mais pour la cryptographie ou les statistiques, utilisez une boucle de rejet ou un vrai générateur.