web-dev-qa-db-fra.com

srand () - pourquoi l'appeler une seule fois?

Cette question concerne un commentaire dans cette question Méthode recommandée pour initialiser srand? Le premier commentaire dit que srand() ne devrait être appelé qu'une fois dans une application. Pourquoi est-ce?

66
Lipika Deka

Cela dépend de ce que vous essayez d’atteindre.

La randomisation est effectuée comme une fonction qui a une valeur de départ, à savoir le germe .

Donc, pour la même graine, vous obtiendrez toujours la même séquence de valeurs.

Si vous essayez de définir la valeur de départ chaque fois que vous avez besoin d'une valeur aléatoire et que la valeur de départ est identique, vous obtiendrez toujours la même valeur "aléatoire".

La graine est généralement prise à partir de l'heure actuelle, à savoir les secondes, comme dans time(NULL). Ainsi, si vous définissez toujours la graine avant de prendre le nombre aléatoire, vous obtiendrez le même numéro tant que vous appelez plusieurs fois le combo srand/Rand dans la même seconde .

Pour éviter ce problème, srand est défini une seule fois par application, car il est douteux que deux des instances de l'application soient initialisées à la même seconde; chaque instance aura alors une séquence différente de nombres aléatoires.

Cependant, il est possible que vous exécutiez votre application plusieurs fois en une seconde (particulièrement s'il s'agit d'une application courte, d'un outil de ligne de commande ou d'un outil similaire), vous devrez alors recourir à un autre moyen de choisir une application. graine (à moins que la même séquence dans différentes instances d’application vous convient). Mais comme je l'ai dit, cela dépend du contexte d'utilisation de votre application.

En outre, vous pouvez essayer d’augmenter la précision à quelques microsecondes (en minimisant le risque que la même graine soit produite), nécessite (sys/time.h):

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
93
Kornelije Petak

Les nombres aléatoires sont en réalité pseudo aléatoires. Une graine est définie en premier, à partir de laquelle chaque appel de Rand obtient un nombre aléatoire et modifie l'état interne. Ce nouvel état est utilisé lors du prochain appel Rand pour obtenir un autre numéro. Etant donné qu'une certaine formule est utilisée pour générer ces "nombres aléatoires", la définition d'une certaine valeur de valeur de départ après chaque appel de Rand renverra le même numéro à partir de l'appel. Par exemple, srand (1234); Rand (); renverra la même valeur. L'initialisation une fois que l'état initial avec la valeur de départ génèrera suffisamment de nombres aléatoires car vous ne définissez pas l'état interne avec srand, ce qui rend les nombres plus probables d'être aléatoires.

Généralement, nous utilisons la valeur de secondes renvoyée par time (NULL) lors de l'initialisation de la valeur de départ. Supposons que srand (time (NULL)); est dans une boucle. Alors la boucle peut itérer plus d'une fois en une seconde. Par conséquent, le nombre d'itérations de la boucle dans la boucle lors d'un second appel Rand dans la boucle retournera le même "nombre aléatoire", ce qui n'est pas souhaité. L'initialiser une fois au début du programme définira la graine une fois. Chaque fois que Rand est appelé, un nouveau numéro est généré et l'état interne est modifié. Le prochain appel Rand renvoie donc un nombre suffisamment aléatoire.

Par exemple, ce code de http://linux.die.net/man/3/Rand :

static unsigned long next = 1;
/* Rand_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

L'état interne next est déclaré comme global. Chaque appel myrand modifiera l'état interne, le mettra à jour et renverra un nombre aléatoire. Chaque appel de myrand aura une valeur next différente; par conséquent, la méthode renverra les numéros différents à chaque appel. 

Regardez l'implémentation mysrand; il définit simplement la valeur de départ que vous transmettez à next. Par conséquent, si vous définissez la même valeur next à chaque fois avant d'appeler Rand, elle retournera la même valeur aléatoire, en raison de la formule identique appliquée, ce qui n'est pas souhaitable, car la fonction est définie comme étant aléatoire.

Mais en fonction de vos besoins, vous pouvez définir la valeur de départ pour générer la même "séquence aléatoire" à chaque exécution, par exemple pour un repère ou d'autres.

22
phoxis

Réponse courte: appeler srand() est pas comme "lancer les dés" pour le générateur de nombres aléatoires. Ce n'est pas non plus comme mélanger un jeu de cartes. Si quoi que ce soit, c'est plus comme couper un jeu de cartes.

Pensez-y comme ça. Rand() traite depuis un grand jeu de cartes, et chaque fois que vous l'appelez, il ne fait que choisir la carte suivante sur le dessus du jeu, vous donner la valeur et la renvoyer au bas du jeu. (Oui, cela signifie que la séquence "aléatoire" se répétera au bout d'un moment. C'est un très gros deck, cependant: typiquement 4 294 967 296 cartes.)

De plus, chaque fois que votre programme est exécuté, un jeu de cartes tout neuf est acheté dans la boutique du jeu. Et chaque nouveau jeu de cartes suit toujours la même séquence. Ainsi, à moins que vous ne fassiez quelque chose de spécial, chaque fois que votre programme sera exécuté, il obtiendra exactement les mêmes nombres "aléatoires" de Rand().

Maintenant, vous pourriez dire: "Bon, alors comment puis-je mélanger le jeu?" Et la réponse est (du moins en ce qui concerne Rand et srand), il n’ya pas moyen de mélanger les cartes.

Alors, que fait srand? D'après l'analogie que je suis en train de construire ici, appeler srand(n) revient en gros à dire "coupe le pont n cartes du dessus". Mais attendez, encore une chose: c’est en fait prenez un autre jeu de cartes tout neuf et coupez-le n cartes du haut.

Donc, si vous appelez srand(n), Rand(), srand(n), Rand(), ..., avec la même n à chaque fois, vous n'obtiendrez pas simplement une séquence pas très aléatoire, vous obtiendrez en fait le même numéro de Rand() à chaque fois. . (Pas nécessairement le même numéro que vous avez donné à srand, mais le même numéro renvoyé de Rand encore et encore.)

Donc, le mieux que vous puissiez faire est de couper le jeu une fois, c'est-à-dire, appelez srand() une fois, au début de votre programme, avec une n raisonnablement aléatoire, pour que vous commenciez à une autre aléatoire place dans le grand pont à chaque fois que votre programme est exécuté.

[P.S. Oui, je sais, dans la vraie vie, lorsque vous achetez un jeu de cartes flambant neuf, c'est généralement dans l'ordre et non dans un ordre aléatoire. Pour que l'analogie fonctionne, j'imagine que chaque paquet que vous achetez dans le magasin de jeu est dans un ordre apparemment aléatoire, mais exactement le même ordre apparemment aléatoire que tous les autres jeux de cartes que vous achetez dans le même magasin. Un peu comme les jeux de cartes mélangés à l'identique qu'ils utilisent dans les tournois de bridge.]

7
Steve Summit

La raison en est que srand() définit l'état initial du générateur aléatoire et que toutes les valeurs générées par ce dernier ne sont "qu'assez aléatoires" si vous ne touchez pas vous-même à l'état intermédiaire.

Par exemple, vous pourriez faire:

int getRandomValue()
{
    srand(time(0));
    return Rand();
}

et ensuite, si vous appelez cette fonction à plusieurs reprises de sorte que time() renvoie les mêmes valeurs dans les appels adjacents, vous obtenez simplement la même valeur générée - cela est voulu par la conception même.

7
sharptooth

Une solution plus simple permettant d’utiliser srand() pour générer différentes sources pour des instances d’application exécutées à la même seconde est la suivante.

srand(time(NULL)-getpid());

Cette méthode rend votre graine très proche de l’aléatoire car il n’ya aucun moyen de deviner à quelle heure votre fil a commencé et le pid sera différent également.

2
achoora

srand ensemence le générateur de nombres pseudo-aléatoires. Si vous appelez plus d'une fois, vous réamorcerez le RNG. Et si vous l'appelez avec le même argument, il redémarrera la même séquence.

Pour le prouver, si vous faites quelque chose de simple comme:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", Rand());
    }
}

vous verrez le même numéro imprimé 100 fois.

2
Foo Bah

1\Il semble que chaque fois que Rand () s'exécute, il créera une nouvelle graine pour le prochain Rand ().

2\Si srand () s'exécute plusieurs fois, le problème est que si les deux exécutions ont lieu en une seconde (le temps (NULL) ne change pas), le prochain Rand () sera identique à Rand () srand ().

0
imoc