J'ai besoin d'une fonction qui générerait un entier aléatoire dans une plage donnée (y compris les valeurs de bordure). Je n'ai pas d'exigences déraisonnables en matière de qualité/aléatoire, j'ai quatre exigences:
J'ai actuellement le code C++ suivant:
output = min + (Rand() * (int)(max - min) / Rand_MAX)
Le problème est que ce n'est pas vraiment uniforme - max est renvoyé uniquement lorsque Rand () = Rand_MAX (pour Visual C++, il s'agit de 1/32727). Ceci est un problème majeur pour les petites plages telles que <-1, 1>, où la dernière valeur n’est presque jamais renvoyée.
J'ai donc saisi le stylo et le papier et proposé la formule suivante (qui repose sur l'astuce d'arrondi de (int) (n + 0,5) entier):
Mais cela ne me donne toujours pas une distribution uniforme. Des analyses répétées avec 10000 échantillons me donnent un rapport de 37:50:13 pour les valeurs -1, 0. 1.
Pourriez-vous s'il vous plaît suggérer une meilleure formule? (ou même fonction de générateur de nombres pseudo-aléatoires entiers)
Une solution distribuée rapide, un peu meilleure que la vôtre, mais qui n’est toujours pas correctement uniforme est
output = min + (Rand() % static_cast<int>(max - min + 1))
Sauf lorsque la taille de la plage est une puissance de 2, cette méthode produit polarisé non uniforme distribué nombres quelle que soit la qualité de Rand()
. Pour un test complet de la qualité de cette méthode, veuillez lisez ceci .
La réponse la plus simple (et donc la meilleure) C++ (utilisant la norme 2011) est
#include <random>
std::random_device rd; // only used once to initialise (seed) engine
std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased
auto random_integer = uni(rng);
Pas besoin de réinventer la roue. Pas besoin de s'inquiéter de partialité. Pas besoin de s'inquiéter d'utiliser le temps comme une graine aléatoire.
Si votre compilateur prend en charge C++ 0x et que son utilisation est une option pour vous, le nouvel en-tête standard <random>
répondra probablement à vos besoins. Il a un uniform_int_distribution
de haute qualité qui accepte les limites minimales et maximales (inclus si vous le souhaitez), et vous pouvez choisir parmi différents générateurs de nombres aléatoires à brancher sur cette distribution.
Voici un code qui génère un million de int
s aléatoires et uniformément répartis dans [-57, 365]. J'ai utilisé les nouvelles installations std <chrono>
pour la chronométrer car vous avez mentionné que les performances sont une préoccupation majeure pour vous.
#include <iostream>
#include <random>
#include <chrono>
int main()
{
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::duration<double> sec;
Clock::time_point t0 = Clock::now();
const int N = 10000000;
typedef std::minstd_Rand G;
G g;
typedef std::uniform_int_distribution<> D;
D d(-57, 365);
int c = 0;
for (int i = 0; i < N; ++i)
c += d(g);
Clock::time_point t1 = Clock::now();
std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
return c;
}
Pour moi (Intel Core i5 à 2,8 GHz), cela affiche:
2.10268e + 07 nombres aléatoires par seconde.
Vous pouvez utiliser le générateur en transmettant un int à son constructeur:
G g(seed);
Si vous trouvez plus tard que int
ne couvre pas la plage dont vous avez besoin pour votre distribution, vous pouvez remédier à cela en modifiant le uniform_int_distribution
comme suit (par exemple, en long long
):
typedef std::uniform_int_distribution<long long> D;
Si vous trouvez par la suite que le minstd_Rand
n'est pas un générateur de qualité suffisante, vous pouvez également l'échanger facilement. Par exemple.:
typedef std::mt19937 G; // Now using mersenne_twister_engine
Avoir un contrôle séparé sur le générateur de nombres aléatoires, et la distribution aléatoire peut être assez libératrice.
J'ai également calculé (non montré) les 4 premiers "moments" de cette distribution (en utilisant minstd_Rand
) et les ai comparés aux valeurs théoriques dans le but de quantifier la qualité de la distribution. :
min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001
(Le préfixe x_
fait référence à "attendu")
Divisons le problème en deux parties:
n
compris entre 0 et (max-min).La première partie est évidemment la plus difficile. Supposons que la valeur de retour de Rand () est parfaitement uniforme. L'utilisation de modulo ajoutera un biais aux premiers (Rand_MAX + 1) % (max-min+1)
nombres. Donc, si nous pouvions changer magiquement Rand_MAX
en Rand_MAX - (Rand_MAX + 1) % (max-min+1)
, il n'y aurait plus de biais.
Il s'avère que nous pouvons utiliser cette intuition si nous sommes disposés à autoriser le pseudo-non-déterminisme dans la durée d'exécution de notre algorithme. Chaque fois que Rand () renvoie un nombre trop grand, nous demandons simplement un autre nombre aléatoire jusqu'à obtenir un nombre suffisamment petit.
Le temps d'exécution est maintenant géométriquement réparti , avec la valeur attendue 1/p
où p
est la probabilité d'obtenir un nombre suffisamment petit du premier coup. Puisque Rand_MAX - (Rand_MAX + 1) % (max-min+1)
est toujours inférieur à (Rand_MAX + 1) / 2
, nous savons que p > 1/2
, de sorte que le nombre d'itérations attendu sera toujours inférieur à deux pour toute plage. Avec cette technique, il devrait être possible de générer des dizaines de millions de nombres aléatoires en moins d’une seconde sur un processeur standard.
MODIFIER:
Bien que ce qui précède soit techniquement correct, la réponse de DSimon est probablement plus utile dans la pratique. Vous ne devriez pas implémenter ce genre de choses vous-même. J'ai vu de nombreuses implémentations d'échantillonnage de rejet et il est souvent très difficile de voir si c'est correct ou non.
Qu'en est-il du Mersenne Twister ? L'implémentation de boost est plutôt facile à utiliser et est bien testée dans de nombreuses applications réelles. Je l'ai moi-même utilisé dans plusieurs projets académiques tels que l'intelligence artificielle et les algorithmes évolutifs.
Voici leur exemple où ils créent une fonction simple pour lancer un dé à six faces:
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
boost::mt19937 gen;
int roll_die() {
boost::uniform_int<> dist(1, 6);
boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
return die();
}
Oh, et voici un peu plus de proxénétisme de ce générateur juste au cas où vous n'êtes pas convaincu que vous devriez l'utiliser sur la Rand()
très inférieure::
Le Mersenne Twister est un générateur de "nombres aléatoires" inventé par Makoto Matsumoto et Takuji Nishimura; leur site Web inclut de nombreuses implémentations de l'algorithme.
Le Twister de Mersenne est essentiellement un très grand registre à décalage à rétroaction linéaire. L'algorithme fonctionne sur un germe de 19 937 bits, stocké dans un tableau de 624 éléments d'entiers non signés de 32 bits. La valeur 2 ^ 19937-1 est un nombre premier de Mersenne; la technique de manipulation de la graine est basée sur un ancien algorithme de "torsion" - d'où le nom "Mersenne Twister".
Un aspect attrayant du Twister Mersenne est son utilisation d'opérations binaires - par opposition à une multiplication prenant du temps - pour générer des nombres. L'algorithme a également une très longue période et une bonne granularité. Il est à la fois rapide et efficace pour les applications non cryptographiques.
int RandU(int nMin, int nMax)
{
return nMin + (int)((double)Rand() / (Rand_MAX+1) * (nMax-nMin+1));
}
Ceci correspond à un mappage de 32 768 entiers sur des entiers (nMax-nMin + 1). La cartographie sera assez bonne si (nMax-nMin + 1) est petite (comme dans vos besoins). Notez toutefois que si (nMax-nMin + 1) est grand, le mappage ne fonctionnera pas (par exemple, vous ne pouvez pas mapper les valeurs 32768 sur 30000 avec la même probabilité). Si de telles plages sont nécessaires, vous devez utiliser une source aléatoire 32 bits ou 64 bits au lieu de Rand () 15 bits ou ignorer les résultats de Rand () qui sont hors limites.
Voici une version non biaisée qui génère des nombres dans [low, high]
:
int r;
do {
r = Rand();
} while (r < ((unsigned int)(Rand_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;
Si votre plage est relativement petite, il n'y a aucune raison de mettre en cache le côté droit de la comparaison dans la boucle do
.
Je recommande la bibliothèque Boost.Random , elle est très détaillée et bien documentée, vous permet de spécifier explicitement la distribution souhaitée et, dans les scénarios non cryptographiques, vous pouvez réellement surperformer un exemple typique. Implémentation de la bibliothèque C Rand.
supposons que min et max sont des valeurs int, [et] signifie que cette valeur est comprise, (et) signifie ne pas inclure cette valeur, utilisez ci-dessus pour obtenir la bonne valeur à l'aide de c ++ Rand ()
référence: pour () [] définir, visiter:
https://en.wikipedia.org/wiki/Interval_ (mathématiques)
pour Rand et srand function ou Rand_MAX define, visitez:
http://en.cppreference.com/w/cpp/numeric/random/Rand
[min max]
int randNum = Rand() % (max - min + 1) + min
(min max]
int randNum = Rand() % (max - min) + min + 1
[min max)
int randNum = Rand() % (max - min) + min
(min max)
int randNum = Rand() % (max - min - 1) + min + 1
Dans ce fil, l’échantillonnage de rejet a déjà été abordé, mais j’ai voulu proposer une optimisation basée sur le fait que Rand() % 2^something
n’introduit aucun biais, comme indiqué plus haut.
L'algorithme est vraiment simple:
Voici mon exemple de code:
int randInInterval(int min, int max) {
int intervalLen = max - min + 1;
//now calculate the smallest power of 2 that is >= than `intervalLen`
int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));
int randomNumber = Rand() % ceilingPowerOf2; //this is "as uniform as Rand()"
if (randomNumber < intervalLen)
return min + randomNumber; //ok!
return randInInterval(min, max); //reject sample and try again
}
Cela fonctionne bien, en particulier pour les petits intervalles, car la puissance de 2 sera "plus proche" de la longueur réelle de l’intervalle, ce qui réduira le nombre de manquements.
PS
Évidemment, éviter la récursivité serait plus efficace (nul besoin de calculer à plusieurs reprises le plafond de logarithmique ..), mais j’ai pensé que c’était plus lisible pour cet exemple.