Le générateur de nombres pseudo aléatoires donne la même première sortie mais se comporte comme prévu

En utilisant la classe aléatoire et une graine de temps (NULL), la dissortingbution uniforme donne toujours la même première sortie, même avec des compilations différentes, mais après la première sortie se comporte comme vous le voudriez un générateur de nombres pseudo-aléatoires.

Est-ce par construction ou est-ce que je ne l’utilise pas correctement?

MWE:

#include  #include  #include  using namespace std; default_random_engine gen(time(NULL)); uniform_int_dissortingbution dist(10,200); int main() { for(int i = 0; i < 5; i++) cout<<dist(gen)<<endl; return 0; } 

Les trois premières fois où j’ai exécuté ce programme, je reçois les résultats suivants:

 57 134 125 136 112 

Avant la deuxième tentative, j’ai décidé de supprimer la ligne vide entre uniform_int_dissortingbution et int main() juste pour voir si la graine était basée sur le temps de compilation, comme vous pouvez le constater, cela importait peu.

 57 84 163 42 146 

Je cours à nouveau:

 57 73 181 160 46 

Donc, lors de mes courses, je continue d’obtenir les 57 premiers, ce qui bien sûr n’est pas la fin du monde. Si je veux des sorties différentes, je peux jeter la première sortie. Mais cela pose la question de savoir si cela est intentionnel (si oui pourquoi?) Ou si j’utilise mal le générateur d’une manière ou d’une autre (si oui comment?).

Je ne suis pas sûr de ce qui ne va pas (encore!), Mais vous pouvez toujours initialiser avec le temps comme suit sans heurter le problème (emprunté à partir d’ ici ).

 #include  #include  #include  #include  using namespace std; unsigned seed1 = std::chrono::system_clock::now().time_since_epoch().count(); default_random_engine gen(seed1); //gen(time(NULL)); uniform_int_dissortingbution dist(10,200); int main() { for(int i = 0; i < 5; i++) cout< 

Vous pouvez également utiliser le périphérique aléatoire, qui n'est pas déterminant (il vole des informations temporelles sur vos frappes de touche, vos mouvements de souris et d'autres sources pour générer des nombres imprévisibles). C’est la graine la plus puissante que vous puissiez choisir, mais l’horloge de votre ordinateur est la meilleure solution si vous n’avez pas besoin de garanties solides, car l’ordinateur peut ne plus être "aléatoire" si vous l’utilisez trop souvent (il faut de nombreuses touches et souris mouvements pour générer un seul nombre vraiment aléatoire).

 std::random_device rd; default_random_engine gen(rd()); 

Fonctionnement

 cout< 

sur ma machine génère

 1413844318 1413844318131372773 3523898368 

la bibliothèque chrono fournit donc un nombre considérablement plus important et un nombre qui évolue plus rapidement (en nanosecondes) que la bibliothèque ctime . random_device produit des nombres non déterministes qui sont random_device sur toute la carte. Il semble donc que les semences ctime soient peut-être trop proches les unes des autres et correspondent donc en partie au même état interne?

J'ai créé un autre programme qui ressemble à ceci:

 #include  #include  using namespace std; int main(){ int oldval = -1; unsigned int oldseed = -1; cout<<"Seed\tValue\tSeed Difference"< dist(10,200); int val = dist(gen); if(val!=oldval){ cout< 

Comme vous pouvez le constater, ceci affiche simplement la première valeur de sortie pour chaque graine aléatoire possible jusqu'à l'heure actuelle, ainsi que la graine et le nombre de graines précédentes ayant la même valeur. Un extrait de la sortie ressemble à ceci:

 Seed Value Seed Difference 0 10 1 669 11 669 1338 12 669 2007 13 669 2676 14 669 3345 15 669 4014 16 669 4683 17 669 5352 18 669 6021 19 669 6690 20 669 7359 21 669 8028 22 669 8697 23 669 9366 24 669 10035 25 669 10704 26 669 11373 27 669 12042 28 669 12711 29 669 13380 30 669 14049 31 669 

Donc, pour chaque nouveau premier numéro, il y a 669 graines qui donnent ce premier numéro. Comme les deuxième et troisième nombres sont différents, nous générons toujours des états internes uniques. Je pense que nous devrions en savoir beaucoup plus sur default_random_engine afin de comprendre ce qui est spécial à propos de 669 (qui peut être pris en compte dans 3 et 223).

Cela étant, il est clair que les bibliothèques chrono et random_device fonctionnent mieux: les graines qu'elles génèrent sont toujours séparées de plus de 669. Gardez à l'esprit que même si le premier nombre est le même, ce qui compte dans de nombreux programmes, c'est que la séquence de nombres générée par distinct.

Utiliser std :: default_random_engine, c’est comme dire “Surprenez-moi!” dans un mauvais restaurant. La seule chose dont vous êtes certain, c’est que le résultat sera médiocre – car les générateurs fournis par sont tous défaillants – mais vous ne saurez même pas quels défauts particuliers vous devez traiter.

Le Twister Mersenne peut être un choix décent si – et seulement si – il est correctement ensemencé, et c’est là que réside le problème. Idéalement, chaque bit de la graine devrait affecter chaque bit de l’état du générateur résultant avec une probabilité égale; comme vous l’avez découvert, ce n’est tout simplement pas le cas dans les implémentations courantes de std :: mersenne_twister_engine.

Les Twisters de Mersenne sont normalement initialisés avec la sortie d’un PRNG plus simple, qui a été à son tour ensemencé par l’entropie disponible. Cela étend efficacement l’entropie des semences du PRNG plus simple sur l’énorme état du twister. Les fabricants de la norme ont soigneusement prévu l’interface seed_seq à cette fin; Cependant, il semble que la bibliothèque ne contienne aucun adaptateur pour utiliser un générateur en tant que séquence de départ.

Il y a aussi la discordance entre deux conceptions différentes de l’ensemencement. Du côté du générateur, la fonction d’ensemencement devrait prendre l’entropie qui a été transmise et la mapper fidèlement sur l’état du générateur, en s’assurant qu’aucune entropie n’est perdue au cours du processus. Du côté de l’utilisateur, c’est “Prenez ces nombres et donnez-moi des séquences très différentes”, où “ces nombres” correspond à la sortie {1, 2, 3, …} ou clock ().

En d’autres termes, l’entropie de la graine est proposée sous une forme qui ne convient pas pour initialiser directement l’état du générateur; les petites différences entre les semences donnent de petites différences d’état. Ceci est particulièrement problématique avec d’énormes générateurs retardés comme le Mersenne Twister ou le Fibonacci retardé qui alimente les générateurs std :: ranluxXX.

Une fonction de mélange de bits – une fonction bijective dans laquelle chaque bit de la sortie dépend de chaque bit de l’entrée avec une probabilité égale – peut aider à créer des semences telles que les sorties 1, 2, 3 ou clock () plus utiles pour l’ensemencement. Le mélangeur hash murmur se rapproche de cet idéal en réalisant une diffusion presque parfaite (version 32 bits illustrée):

 uint32_t murmur_mix32 (uint32_t x) { x ^= x >> 16; x *= 0x85EBCA6B; x ^= x >> 13; x *= 0xC2B2AE35; x ^= x >> 16; return x; } 

La fonction est bijective, elle ne perd donc aucune entropie. Cela signifie que vous pouvez l’utiliser pour améliorer n’importe quelle graine sans risquer d’aggraver les choses.

Une autre solution rapide – sans l’effort de créer un seed_seq – consiste à appeler discard () sur le générateur avec un paramètre qui dépend de la graine (mélange murmuré). Cependant, l’effet sur d’énormes générateurs comme le Mersenne Twister est quelque peu limité, car leur état évolue extrêmement lentement et qu’ils ont besoin de centaines de milliers d’itérations pour se remettre complètement des États déficients.

la graine que vous utilisez pourrait introduire un biais, si l’utilisation d’une graine différente donne les mêmes résultats, le générateur lui-même n’est pas correctement écrit.

Je suggérerais de tester avec différentes semences pour tirer une conclusion.