Le problème le plus fondamental de votre application de test est que vous appelez srand
une fois, puis rand
une fois et que vous quittez.
Le but de la srand
fonction 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 à srand
dans deux applications différentes (avec le même srand
/ rand
implémentation), vous obtiendrez exactement la même séquence de rand()
valeurs lues par la suite dans les deux applications.
Cependant, dans votre exemple d'application, la séquence pseudo-aléatoire se compose d'un seul élément - le premier élément d'une séquence pseudo-aléatoire générée à partir de la valeur de départ égale à l'heure de second
précision actuelle. Qu'attendez-vous alors de la sortie?
Évidemment, lorsque vous exécutez l'application sur la même seconde - vous utilisez la même valeur de départ - votre résultat est donc bien sûr le même (comme Martin York l'a déjà mentionné dans un commentaire à la question).
En fait, vous devriez appeler srand(seed)
une fois, puis appeler rand()
plusieurs fois et analyser cette séquence - elle devrait paraître aléatoire.
ÉDITER:
Oh je comprends. Apparemment, la description verbale ne suffit pas (peut-être la barrière de la langue ou quelque chose du genre ... :)).
D'ACCORD. Exemple de code C à l'ancienne basé sur les mêmes srand()/rand()/time()
fonctions que celles utilisées 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;
}
^^^ CETTE séquence d'une seule exécution du programme est censée paraître aléatoire.
EDIT2:
Lorsque vous utilisez une bibliothèque standard C ou C ++, il est important de comprendre qu'à partir de maintenant, il n'y a pas une seule fonction ou classe standard produisant des données réellement aléatoires de manière définitive (garantie par le standard). Le seul outil standard qui aborde ce problème est std :: random_device qui malheureusement ne fournit toujours pas de garanties d'aléa réel.
En fonction de la nature de l'application, vous devez d'abord décider si vous avez vraiment besoin de données vraiment aléatoires (imprévisibles). Le cas notable où vous avez certainement besoin d'un vrai hasard est la sécurité de l'information - 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 distinct.
Dans la plupart des cas, le générateur de nombres pseudo-aléatoires est suffisant - 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 requise - par exemple, dans les jeux, vous pouvez choisir de générer exactement les mêmes cartes au moment de l'exécution pour éviter de stocker beaucoup de données.
La question originale et la multitude récurrente de questions identiques / similaires (et même de nombreuses «réponses» erronées à celles-ci) 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 dans le premier endroit ET pour réaliser que les générateurs de nombres pseudo-aléatoires ne sont PAS utilisés de la même manière que vous pourriez utiliser de vrais générateurs de nombres aléatoires.
Intuitivement lorsque vous demandez un nombre aléatoire - le résultat retourné ne devrait pas dépendre des valeurs précédemment renvoyées et ne devrait pas dépendre si quelqu'un a demandé quelque chose avant et ne devrait pas dépendre à quel moment et par quel processus et sur quel ordinateur et de quel générateur et dans quelle galaxie il a été demandé. C'est ce que signifie le mot «aléatoire» après tout - être imprévisible et indépendant de quoi que ce soit - sinon ce n'est plus aléatoire, non? Avec cette intuition, il est naturel de rechercher sur le Web des sorts magiques à lancer pour obtenir un tel nombre aléatoire dans n'importe quel contexte possible.
^^^ CE type d'attentes intuitives EST TRÈS FAUX et nuisibles dans tous les cas impliquant des générateurs de nombres pseudo-aléatoires - bien qu'ils soient raisonnables pour de vrais nombres aléatoires.
Bien que la notion significative de "nombre aléatoire" existe, il n'y a pas de "nombre pseudo-aléatoire". Un générateur de nombres pseudo-aléatoires produit en fait une séquence de nombres pseudo-aléatoires .
Lorsque les experts parlent de la qualité du 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 pouvez produire une mauvaise séquence résultante - bien qu'ils génèrent de bonnes séquences chacune séparément (ces deux bonnes séquences peuvent simplement se corréler l'une à l'autre et donc se combiner mal).
La séquence pseudo-aléatoire est en fait toujours déterministe (prédéterminée par son algorithme et ses paramètres initiaux) c'est-à-dire qu'elle n'a en fait rien d'aléatoire.
Spécifiquement rand()
/ srand(s)
paire de fonctions fournit une séquence de nombres pseudo-aléatoires singulière par processus non thread-safe (!) Générée avec un algorithme défini par l'implémentation. La fonction rand()
produit des valeurs dans la plage [0, RAND_MAX]
.
Citation de la norme C11:
La srand
fonction utilise l'argument comme valeur de départ pour une nouvelle séquence de nombres pseudo-aléatoires à renvoyer par les appels ultérieurs à rand
. Si
srand
est alors appelé avec la même valeur de départ, la séquence de nombres pseudo-aléatoires doit être répétée. Si rand
est appelé avant que des appels srand
aient été effectués, la même séquence doit être générée que lors du srand
premier appel avec une valeur de départ de 1.
Beaucoup de gens s'attendent raisonnablement à ce que rand()
cela produise une séquence de nombres semi-indépendants uniformément distribués dans l'intervalle 0
de RAND_MAX
. Eh bien, il devrait très certainement (sinon c'est inutile) mais malheureusement non seulement la norme ne l'exige pas - il existe même une clause de non-responsabilité explicite qui déclare "il n'y a aucune garantie quant à la qualité de la séquence aléatoire produite" . Dans certains cas historiques rand
/ la srand
mise en œuvre était en effet de très mauvaise qualité. Même si dans les implémentations modernes, il est très probablement assez bon - mais la confiance est brisée et pas facile à récupérer. Outre sa nature non thread-safe, son utilisation en toute sécurité dans les applications multi-threads est délicate et limitée (toujours possible - vous pouvez simplement les utiliser à partir d'un thread dédié).
Le nouveau modèle de classe std :: mersenne_twister_engine <> (et ses typedefs pratiques - std::mt19937
/ std::mt19937_64
avec une bonne combinaison de paramètres de modèle) fournit un générateur de nombres pseudo-aléatoires par objet 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 une bibliothèque standard compatible C ++ 11. L'avantage de cette classe est sa séquence de sortie de haute qualité prévisible et sa cohérence totale entre les implémentations.
Il y a aussi plus de moteurs PRNG définis dans le standard C ++ 11 - std :: linear_congruential_engine <> (historiquement utilisé comme srand/rand
algorithme de qualité équitable dans certaines implémentations de bibliothèques standard C) et std :: subtract_with_carry_engine <> . Ils génèrent également des séquences de sortie par objet entièrement définies dépendant des paramètres.
Exemple de remplacement moderne C ++ 11 du 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;
}