web-dev-qa-db-fra.com

Générer un entier aléatoire à partir d'une plage

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 besoin que ça soit rapide. Mon projet doit générer des millions (voire parfois des dizaines de millions) de nombres aléatoires et ma fonction de générateur actuelle s'est révélée être un goulot d'étranglement.
  • J'ai besoin qu'il soit raisonnablement uniforme (l'utilisation de Rand () convient parfaitement).
  • les plages min-max peuvent être comprises entre <0, 1> et <-32727, 32727>.
  • il doit être ensemencé.

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):

enter image description here

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)

148
Matěj Zábský

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 .

95
Mark B

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.

277
Walter

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 ints 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")

60
Howard Hinnant

Divisons le problème en deux parties:

  • Générez un nombre aléatoire n compris entre 0 et (max-min).
  • Ajouter min à ce nombre

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/pp 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.

15
Jørgen Fogh

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.

13
Aphex
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.

11
Lior Kogan

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.

4
Jeremiah Willcock

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.

3
DSimon

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
1
Huang Kun

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:

  • calculer la plus petite puissance de 2 supérieure à la longueur de l'intervalle
  • randomiser un nombre dans cet "nouvel" intervalle
  • renvoie ce nombre s'il est inférieur à la longueur de l'intervalle d'origine
    • rejeter autrement

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.

0
Pado