J'ai observé que la rand()
fonction de bibliothèque lorsqu'elle est appelée une seule fois dans une boucle, elle produit presque toujours des nombres positifs.
for (i = 0; i < 100; i++) {
printf("%d\n", rand());
}
Mais lorsque j'ajoute deux rand()
appels, les nombres générés ont maintenant plus de nombres négatifs.
for (i = 0; i < 100; i++) {
printf("%d = %d\n", rand(), (rand() + rand()));
}
Quelqu'un peut-il expliquer pourquoi je vois des nombres négatifs dans le deuxième cas?
PS: j'initialise la graine avant la boucle comme srand(time(NULL))
.
rand()
ne peut pas être négatif ...RAND_MAX
pour votre compilateur? Vous pouvez généralement le trouver dansstdlib.h
. (Drôle: en vérifiantman 3 rand
, il porte la description d'une ligne "mauvais générateur de nombres aléatoires".)abs(rand()+rand())
. Je préfère avoir un UB positif que négatif! ;)Réponses:
rand()
est défini pour renvoyer un entier entre0
etRAND_MAX
.pourrait déborder. Ce que vous observez est probablement le résultat d' un comportement indéfini provoqué par un débordement d'entier.
la source
a + b > a
s'ils le saventb > 0
. Ils peuvent également supposer que s'il y a une instruction exécutée ultérieurement,a + 5
la valeur actuelle est alors inférieureINT_MAX - 5
. Ainsi, même sur le processeur / interprète complément 2 sans programme pièges, le programme pourrait ne pas se comporter comme siint
s était le complément 2 sans pièges.Le problème est l'ajout.
rand()
renvoie uneint
valeur de0...RAND_MAX
. Donc, si vous en ajoutez deux, vous vous lèverezRAND_MAX * 2
. Si cela dépasseINT_MAX
, le résultat de l'addition déborde la plage valide qu'unint
peut contenir. Le débordement des valeurs signées est un comportement indéfini et peut conduire votre clavier à vous parler en langues étrangères.Comme il n'y a aucun avantage à ajouter deux résultats aléatoires, l'idée simple est simplement de ne pas le faire. Vous pouvez également convertir chaque résultat en
unsigned int
avant l'ajout si cela peut contenir la somme. Ou utilisez un type plus grand. Notez que celong
n'est pas nécessairement plus large queint
, la même chose s'applique àlong long
ifint
est au moins 64 bits!Conclusion: Évitez simplement l'ajout. Il ne fournit pas plus de "caractère aléatoire". Si vous avez besoin de plus de bits, vous pouvez concaténer les valeurs
sum = a + b * (RAND_MAX + 1)
, mais cela nécessite également probablement un type de données plus grand queint
.Comme votre raison indiquée est d'éviter un résultat nul: cela ne peut pas être évité en ajoutant les résultats de deux
rand()
appels, car les deux peuvent être nuls. Au lieu de cela, vous pouvez simplement incrémenter. SiRAND_MAX == INT_MAX
cela ne peut pas être fait enint
. Cependant,(unsigned int)rand() + 1
fera très, très probablement. Probablement (pas définitivement), car cela nécessiteUINT_MAX > INT_MAX
, ce qui est vrai sur toutes les implémentations que je connais (qui couvre plusieurs architectures intégrées, DSP et toutes les plates-formes de bureau, mobiles et serveurs des 30 dernières années).Avertissement:
Bien que déjà saupoudré dans les commentaires ici, veuillez noter que l'ajout de deux valeurs aléatoires n'obtient pas une distribution uniforme, mais une distribution triangulaire comme lancer deux dés: pour obtenir
12
(deux dés) les deux dés doivent s'afficher6
. car11
il existe déjà deux variantes possibles:6 + 5
ou5 + 6
, etc.Donc, l'ajout est également mauvais de cet aspect.
Notez également que les résultats
rand()
générés ne sont pas indépendants les uns des autres, car ils sont générés par un générateur de nombres pseudo-aléatoires . Notez également que la norme ne spécifie pas la qualité ou la distribution uniforme des valeurs calculées.la source
UINT_MAX > INT_MAX != false
la norme est garantie. (Cela semble probable, mais pas sûr si nécessaire). Si c'est le cas, vous pouvez simplement lancer un seul résultat et incrémenter (dans cet ordre!).Ceci est une réponse à une clarification de la question faite en commentaire de cette réponse ,
Le problème était d'éviter 0. Il y a (au moins) deux problèmes avec la solution proposée. L'une est, comme les autres réponses l'indiquent, qui
rand()+rand()
peut invoquer un comportement indéfini. Le meilleur conseil est de ne jamais invoquer un comportement indéfini. Un autre problème est qu'il n'y a aucune garantie quirand()
ne produira pas 0 deux fois de suite.Ce qui suit rejette zéro, évite un comportement indéfini et, dans la grande majorité des cas, sera plus rapide que deux appels à
rand()
:la source
rand() + 1
?rand()
Produisez essentiellement des nombres entre0
etRAND_MAX
, et2 RAND_MAX > INT_MAX
dans votre cas.Vous pouvez moduler avec la valeur maximale de votre type de données pour éviter un débordement. Bien sûr, cela perturbera la distribution des nombres aléatoires, mais
rand
n'est qu'un moyen d'obtenir des nombres aléatoires rapides.la source
Peut-être pourriez-vous essayer une approche plutôt délicate en vous assurant que la valeur renvoyée par la somme de 2 rand () ne dépasse jamais la valeur de RAND_MAX. Une approche possible pourrait être sum = rand () / 2 + rand () / 2; Cela garantirait que pour un compilateur 16 bits avec une valeur RAND_MAX de 32767 même si les deux rand retournent justement 32767, même dans ce cas (32767/2 = 16383) 16383 + 16383 = 32766, cela n'entraînerait donc pas de somme négative.
la source
rand()
ne donneront pas tous les deux zéro, donc le désir d'éviter zéro n'est pas une bonne raison pour ajouter deux valeurs. D'un autre côté, le désir d'avoir une distribution non uniforme serait une bonne raison d'ajouter deux valeurs aléatoires si l'on s'assure qu'aucun débordement ne se produit.Une solution simple (d'accord, appelez ça un "Hack") qui ne produit jamais de résultat nul et ne débordera jamais est:
Cela limitera votre valeur maximale, mais si cela ne vous intéresse pas, cela devrait bien fonctionner pour vous.
la source
rand()
renvoie toujours une valeur non négative). Cependant, je laisserais l'optimisation au compilateur ici.rand
ne sera pas négatif, le décalage sera plus efficace que la division par un entier signé 2. La division par2u
pourrait fonctionner, mais six
c'est le cas, celaint
peut entraîner des avertissements sur la conversion implicite de non signé à signer./ 2
toute façon (je l'ai vu même pour quelque chose comme-O0
, c'est-à-dire sans optimisations explicitement demandées). C'est peut-être l'optimisation la plus triviale et la plus établie du code C. Le point est que la division est bien définie par la norme pour toute la plage entière, pas seulement les valeurs non négatives. Encore une fois: laissez les optimisations au compilateur, écrivez du code correct et clair en premier lieu. C'est encore plus important pour les débutants.rand()
droite de un ou lors de la division2u
que lors de la division par 2, même lors de l'utilisation-O3
. On pourrait raisonnablement dire qu'une telle optimisation est peu probable, mais dire "laisser de telles optimisations au compilateur" impliquerait que les compilateurs seraient susceptibles de les réaliser. Connaissez-vous des compilateurs qui sera en fait?Pour éviter 0, essayez ceci:
Vous devez inclure
limits.h
.la source
rand()
donne 0.1
et2
(tout cela est supposéRAND_MAX == INT_MAX
). J'ai oublié le- 1
.-1
ici ne sert à rien.rand()%INT_MAX+1;
ne générerait toujours que des valeurs dans la plage [1 ... INT_MAX].Alors que ce que tout le monde a dit sur le débordement probable pourrait très bien être la cause du négatif, même lorsque vous utilisez des entiers non signés. Le vrai problème est en fait d'utiliser la fonctionnalité heure / date comme graine. Si vous vous êtes vraiment familiarisé avec cette fonctionnalité, vous saurez exactement pourquoi je dis cela. Car ce qu'il fait vraiment est de donner une distance (temps écoulé) depuis une date / heure donnée. Bien que l'utilisation de la fonctionnalité date / heure comme graine d'un rand () soit une pratique très courante, ce n'est vraiment pas la meilleure option. Vous devriez chercher de meilleures alternatives, car il existe de nombreuses théories sur le sujet et je ne pourrais pas entrer dans toutes. Vous ajoutez à cette équation la possibilité de débordement et cette approche était vouée à l'échec dès le départ.
Ceux qui ont posté le rand () + 1 utilisent la solution la plus utilisée afin de garantir qu'ils n'obtiennent pas de nombre négatif. Mais cette approche n'est vraiment pas la meilleure façon non plus.
La meilleure chose que vous puissiez faire est de prendre le temps supplémentaire pour écrire et utiliser la gestion des exceptions appropriée, et n'ajouter au nombre rand () que si et / ou lorsque vous vous retrouvez avec un résultat nul. Et, pour gérer correctement les nombres négatifs. La fonctionnalité rand () n'est pas parfaite et doit donc être utilisée en conjonction avec la gestion des exceptions pour vous assurer d'obtenir le résultat souhaité.
Prendre le temps et les efforts supplémentaires pour enquêter, étudier et implémenter correctement la fonctionnalité rand () en vaut la peine. Juste mes deux cents. Bonne chance dans vos efforts ...
la source
rand()
ne spécifie pas quelle graine utiliser. La norme ne précise à utiliser un générateur pseudo - aléatoire, et non une relation à tout moment. Il ne précise pas non plus la qualité du générateur. Le problème actuel est clairement le débordement. Notez que celarand()+1
est utilisé pour éviter0
;rand()
ne renvoie pas de valeur négative. Désolé, mais vous avez manqué le point ici. Il ne s'agit pas de la qualité du PRNG. .../dev/random
et d'utiliser un bon PRNG par la suite (vous n'êtes pas sûr de la qualité derand()
glibc) ou de continuer à utiliser l'appareil - au risque de bloquer votre application s'il n'y a pas assez d'entropie disponible. Essayer d'obtenir votre entropie dans l'application pourrait très bien être une vulnérabilité car cela est peut-être plus facile à attaquer. Et maintenant, il s'agit de durcir - pas ici