Le problème le plus fondamental de votre application de test est que vous appelez srand
une fois et ensuite appeler rand
une fois et quitter.
Le but de srand
est d'initialiser la séquence de nombres pseudo-aléatoires avec une graine aléatoire.
Cela signifie que si vous passez la même valeur a srand
dans deux applications différentes (avec la même srand
/ rand
) alors vous obtiendrez exactement la même séquence de rand()
valeurs lues ensuite dans les deux applications.
Cependant, dans votre exemple d'application, la séquence pseudo-aléatoire ne comprend qu'un seul élément - le premier élément d'une séquence pseudo-aléatoire générée à partir d'une graine égale à l'heure actuelle de 1 sec
précision. Qu'espérez-vous voir à la sortie alors ?
Évidemment, lorsque vous exécutez une application à la même seconde, vous utilisez la même valeur d'ensemencement, et le résultat est donc le même, bien entendu (comme Martin York l'a déjà mentionné dans un commentaire sur la question).
En fait, vous devriez appeler srand(seed)
une fois et ensuite appeler rand()
plusieurs fois et analyser cette séquence - elle devrait avoir l'air aléatoire.
AMENDEMENT 1 - exemple de code :
OK, j'ai compris. Apparemment, une description verbale ne suffit pas (peut-être la barrière de la langue ou autre chose... :) ).
Exemple de code C à l'ancienne basé sur la même srand()/rand()/time()
qui a été utilisé dans la question :
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ QUE d'une seule exécution du programme est censée être aléatoire.
Veuillez noter que je ne recommande pas d'utiliser rand
/ srand
en production pour les raisons expliquées ci-dessous et je ne recommande absolument pas d'utiliser la fonction time
comme une graine aléatoire pour les raisons qui, selon l'OMI, devraient déjà être assez évidentes. Ils sont très bien à des fins éducatives et pour illustrer le point parfois, mais pour toute utilisation sérieuse, ils sont pratiquement inutiles.
AMENDEMENT 2 - explication détaillée :
Il est important de comprendre qu'à l'heure actuelle, il y a aucun Les fonctions standard C ou C++ (fonctions ou classes de bibliothèque) produisant des données réellement aléatoires de manière définitive (c'est-à-dire garanties par la norme comme étant réellement aléatoires). La seule fonction standard qui s'approche de ce problème est std::random_device qui, malheureusement, ne garantit toujours pas un caractère aléatoire réel.
Selon la nature de l'application, vous devez d'abord décider si vous avez vraiment besoin de données véritablement aléatoires (imprévisibles). Cas notables alors que vous avez certainement besoin d'un véritable hasard est la sécurité des informations - par exemple, la génération de clés symétriques, de clés privées asymétriques, de valeurs de sel, de jetons de sécurité, etc.
Cependant, les nombres aléatoires de niveau de sécurité constituent une industrie distincte qui mérite un article séparé. Je les aborde brièvement dans cette réponse de la mienne.
Dans la plupart des cas Générateur de nombres pseudo-aléatoires est suffisante - par exemple pour les simulations scientifiques ou les jeux. Dans certains cas, une séquence pseudo-aléatoire définie de manière cohérente est même nécessaire - par exemple, dans les jeux, vous pouvez choisir de générer exactement les mêmes cartes en cours d'exécution pour éviter de stocker beaucoup de données dans votre pack d'installation.
La question initiale et la multitude de questions identiques/similaires (et même de nombreuses "réponses" erronées à ces questions) indiquent qu'il est avant tout important de distinguer les nombres aléatoires des nombres pseudo-aléatoires ET de comprendre ce qu'est une séquence de nombres pseudo-aléatoires en premier lieu ET de réaliser que les générateurs de nombres pseudo-aléatoires ne sont PAS utilisés de la même manière que les vrais générateurs de nombres aléatoires.
Intuitivement lorsque vous demandez un nombre aléatoire, le résultat renvoyé ne doit pas dépendre des valeurs renvoyées précédemment et ne doit pas dépendre si si quelqu'un a demandé quelque chose avant et ne devrait pas dépendre de l'heure à laquelle et par quel processus et sur quel ordinateur et à partir de quel générateur et dans quelle galaxie il a été demandé. C'est ce que le mot "aléatoire" signifie après tout - être imprévisible et indépendant de tout - sinon ce n'est plus du hasard, n'est-ce pas ? Avec cette intuition, il est naturel de chercher sur Internet des formules magiques à lancer pour obtenir un tel nombre aléatoire dans n'importe quel contexte. un tel nombre aléatoire dans n'importe quel contexte possible.
^^^ Ce genre d'attentes intuitives est très mauvais et nuisible. dans toutes les affaires impliquant Générateurs de nombres pseudo-aléatoires - bien qu'elle soit raisonnable pour les vrais nombres aléatoires.
Si la notion de "nombre aléatoire" existe (en quelque sorte), celle de "nombre pseudo-aléatoire" n'existe pas. A Générateur de nombres pseudo-aléatoires produit en fait un nombre pseudo-aléatoire séquence .
La séquence pseudo-aléatoire est en fait toujours déterministe (prédéterminé par son algorithme et ses paramètres initiaux) - c'est-à-dire qu'il n'y a en fait rien d'aléatoire.
Lorsque les experts parlent de la qualité des PRNG, ils parlent en fait des propriétés statistiques de la séquence générée (et de ses sous-séquences notables). Par exemple, si vous combinez deux PRNG de haute qualité en les utilisant tous les deux à tour de rôle, vous risquez de produire une mauvaise séquence résultante, bien qu'ils aient généré de bonnes séquences chacun de leur côté (ces deux bonnes séquences peuvent simplement se corréler l'une à l'autre et donc se combiner mal).
Plus précisément rand()
/ srand(s)
Cette paire de fonctions fournit une séquence de nombres pseudo-aléatoires unique par processus et non sécurisée par les threads ( !), générée à l'aide d'un algorithme défini par l'implémentation. Fonction rand()
produit des valeurs dans l'intervalle [0, RAND_MAX]
.
Citation de la norme C11 (ISO/IEC 9899:2011) :
El srand
utilise l'argument comme une graine pour une nouvelle séquence de pseudo-aléatoires qui seront retournés par les appels ultérieurs à la fonction rand
. Si srand
est ensuite appelé avec la même valeur de semence, la séquence de nombres pseudo-aléatoires est répétée. Si rand
est appelé avant tout appels à srand
ont été effectués, la même séquence est générée que lorsque srand
est d'abord appelé avec une valeur d'amorçage de 1.
Beaucoup de gens s'attendent raisonnablement à ce que rand()
produirait une séquence de nombres semi-indépendants uniformément distribués dans l'intervalle 0
a RAND_MAX
. Il devrait certainement l'être (sinon il est inutile) mais malheureusement, non seulement la norme ne l'exige pas, mais il y a même une clause de non-responsabilité explicite qui stipule ce qui suit "il n'y a aucune garantie quant à la qualité de la séquence aléatoire produite" . Dans certains cas historiques rand
/ srand
La mise en œuvre était en effet de très mauvaise qualité. Même si les implémentations modernes sont probablement assez bonnes, la confiance est rompue et il n'est pas facile de la rétablir. En outre, sa nature non thread-safe rend son utilisation sûre dans les applications multi-threads délicate et limitée (toujours possible - vous pouvez juste les utiliser depuis un thread dédié).
Nouveau modèle de classe std::mersenne_twister_engine<> (et ses typedefs de commodité - std::mt19937
/ std::mt19937_64
avec une bonne combinaison de paramètres de modèle) fournit par objet générateur de nombres pseudo-aléatoires défini dans la norme C++11. Avec les mêmes paramètres de modèle et les mêmes paramètres d'initialisation, différents objets généreront exactement la même séquence de sortie par objet sur n'importe quel ordinateur dans n'importe quelle application construite avec la bibliothèque standard C++11. L'avantage de cette classe est la haute qualité prévisible de la séquence de sortie et la cohérence totale entre les implémentations.
Il existe également d'autres moteurs PRNG définis dans la norme C++11. std::moteur_congruentiel_linéaire<> (historiquement utilisé comme qualité équitable srand/rand
dans certaines implémentations de la bibliothèque standard C) et std::subtract_with_carry_engine<> . Ils génèrent également des séquences de sortie par objet, entièrement définies et dépendantes des paramètres.
Exemple moderne de C++11 remplaçant le code C obsolète ci-dessus :
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
La version du code précédent qui utilise std::uniform_int_distribution<>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}