Relatif au fait d' rand() % n
être loin d' être idéal
Faire rand() % n
a une distribution non uniforme. Vous obtiendrez un nombre disproportionné de certaines valeurs car le nombre de valeurs n'est pas un multiple de 20
Ensuite, il rand()
s'agit généralement d'un générateur congruentiel linéaire (il y en a beaucoup d'autres , juste celui qui est le plus probable implémenté - et avec des paramètres moins qu'idéaux (il existe de nombreuses façons de sélectionner les paramètres)). Le plus gros problème avec cela est que souvent les bits bas (ceux que vous obtenez avec une % 20
expression de type) ne sont pas si aléatoires. Je me souviens d'un rand()
il y a des années où le bit le plus bas alternait de 1
à 0
à chaque appel à rand()
- ce n'était pas très aléatoire.
Depuis la page de manuel de rand (3):
Les versions de rand () et srand () dans la bibliothèque Linux C utilisent les mêmes
générateur de nombres aléatoires comme random () et srandom (), donc l'ordre inférieur
les bits doivent être aussi aléatoires que les bits d'ordre supérieur. Cependant, sur les anciens
rand () implémentations, et sur les implémentations actuelles sur différents
systèmes, les bits d’ordre inférieur sont beaucoup moins aléatoires que les bits
commander des bits. N'utilisez pas cette fonction dans des applications destinées à
portable lorsqu'un bon caractère aléatoire est nécessaire.
Cela peut maintenant être relégué dans l'histoire, mais il est fort possible que vous ayez toujours une mauvaise implémentation de rand () cachée quelque part dans la pile. Dans ce cas, c'est encore tout à fait applicable.
La chose à faire est d'utiliser une bonne bibliothèque de nombres aléatoires (qui donne de bons nombres aléatoires), puis de demander des nombres aléatoires dans la plage souhaitée.
Un exemple d'un bon bit de code aléatoire (à partir de 13h00 dans la vidéo liée)
#include <iostream>
#include <random>
int main() {
std::mt19937 mt(1729); // yes, this is a fixed seed
std::uniform_int_distribution<int> dist(0, 99);
for (int i = 0; i < 10000; i++) {
std::cout << dist(mt) << " ";
}
std::cout << std::endl;
}
Comparez cela à:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
for (int i = 0; i < 10000; i++) {
printf("%d ", rand() % 100);
}
printf("\n");
}
Exécutez ces deux programmes et comparez la fréquence à laquelle certains nombres apparaissent (ou ne s'affichent pas) dans cette sortie.
Vidéo connexe: rand () considéré comme nuisible
Quelques aspects historiques de rand () causant des bogues dans Nethack que l'on devrait regarder et considérer dans ses propres implémentations:
Problème Nethack RNG
Rand () est une fonction très fondamentale pour la génération de nombres aléatoires de Nethack. La façon dont Nethack l'utilise est boguée ou on peut soutenir que lrand48 () produit des nombres pseudo-aléatoires merdiques. (Cependant, lrand48 () est une fonction de bibliothèque utilisant une méthode PRNG définie et tout programme qui l'utilise devrait prendre en compte les faiblesses de cette méthode.)
Le bogue est que Nethack s'appuie (parfois exclusivement comme c'est le cas dans rn (2)) sur les bits inférieurs des résultats de lrand48 (). Pour cette raison, RNG dans tout le jeu fonctionne mal. Ceci est particulièrement visible avant que les actions de l'utilisateur n'introduisent davantage d'aléatoire, c'est-à-dire dans la génération de caractères et la création de premier niveau.
Bien que ce qui précède date de 2003, il convient de garder à l'esprit car il se peut que tous les systèmes exécutant votre jeu ne soient pas un système Linux à jour avec une bonne fonction rand ().
Si vous faites cela par vous-même, vous pouvez tester la qualité de votre générateur de nombres aléatoires en écrivant du code et en testant la sortie avec ent .
Sur les propriétés des nombres aléatoires
Il existe d'autres interprétations de «aléatoire» qui ne sont pas exactement aléatoires. Dans un flux aléatoire de données, il est tout à fait possible d'obtenir deux fois le même nombre. Si vous lancez une pièce (au hasard), il est tout à fait possible d'obtenir deux têtes d'affilée. Ou lancez deux dés et obtenez le même numéro deux fois de suite. Ou faites tourner une roulette et obtenez deux fois le même numéro.
La répartition des nombres
Lors de la lecture d'une liste de chansons, les gens s'attendent à ce que «aléatoire» signifie que la même chanson ou le même artiste ne sera pas joué une deuxième fois de suite. Avoir une playlist jouer les Beatles deux fois de suite est considéré comme «non aléatoire» (bien qu'il soit aléatoire). La perception que pour une liste de lecture de quatre chansons a joué un total de huit fois:
1 3 2 4 1 2 4 3
est plus «aléatoire» que:
1 3 3 2 1 4 4 2
Plus d'informations à ce sujet pour le "mélange" des chansons: Comment mélanger les chansons?
Sur les valeurs répétées
Si vous ne voulez pas répéter des valeurs, il existe une approche différente qui doit être envisagée. Générez toutes les valeurs possibles et mélangez-les.
Si vous appelez rand()
(ou tout autre générateur de nombres aléatoires), vous l'appelez avec remplacement. Vous pouvez toujours obtenir le même numéro deux fois. Une option consiste à lancer les valeurs encore et encore jusqu'à ce que vous en sélectionniez une qui réponde à vos besoins. Je soulignerai que cela a un runtime non déterministe et il est possible que vous vous retrouviez dans une situation où il y a une boucle infinie à moins que vous ne commenciez à faire un traçage arrière plus complexe.
Liste et sélection
Une autre option consiste à générer une liste de tous les états valides possibles, puis à sélectionner un élément aléatoire dans cette liste. Trouvez tous les espaces vides (qui répondent à certaines règles) dans la salle, puis choisissez-en un au hasard dans cette liste. Et puis faites-le encore et encore jusqu'à ce que vous ayez terminé.
Shuffle
L'autre approche consiste à mélanger comme s'il s'agissait d'un jeu de cartes. Commencez par tous les espaces vides dans la salle, puis commencez à les attribuer en distribuant les espaces vides, un par un, à chaque règle / processus en demandant un espace vide. Vous avez terminé lorsque vous n'avez plus de cartes ou que les choses cessent de les demander.