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

158

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 de qualité / aléatoire déraisonnables, j'ai quatre exigences:

  • J'ai besoin que ce soit rapide. Mon projet doit générer des millions (voire des dizaines de millions) de nombres aléatoires et ma fonction de générateur actuelle s'est avérée être un goulot d'étranglement.
  • J'ai besoin qu'il soit raisonnablement uniforme (l'utilisation de rand () est parfaitement bien).
  • les plages min-max peuvent être n'importe quoi de <0, 1> à <-32727, 32727>.
  • il doit pouvoir être semé.

J'ai actuellement le code C ++ suivant:

output = min + (rand() * (int)(max - min) / RAND_MAX)

Le problème est qu'il n'est pas vraiment uniforme - max est renvoyé uniquement lorsque rand () = RAND_MAX (pour Visual C ++, il est 1/32727). C'est un problème majeur pour les petites plages telles que <-1, 1>, où la dernière valeur n'est presque jamais retournée.

J'ai donc pris un stylo et du papier et suis venu avec la formule suivante (qui s'appuie sur l'astuce d'arrondi entier (int) (n + 0.5)):

entrez la description de l'image ici

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.

Pouvez-vous suggérer une meilleure formule? (ou même fonction de générateur de nombres pseudo-aléatoires entiers)

Matěj Zábský
la source
3
@Bill MaGriff: oui. Il a le même problème. Une version simplifiée est la suivante: comment pouvez-vous répartir 10 morceaux de bonbons entre 3 enfants de manière égale (sans casser aucun des bonbons)? La réponse est que vous ne pouvez pas - vous devez en donner trois à chaque enfant, et simplement ne pas donner le dixième à personne.
Jerry Coffin
5
Avez-vous regardé Boost.Random ?
Fred Nurk
3
Consultez l'article d'Andrew Koenig "Un problème simple qui n'est presque jamais résolu correctement": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev
1
@Gene Bushuyev: Andrew et moi insistons sur ce sujet depuis un bon moment maintenant. Voir: groups.google.com/group/comp.lang.c++/browse_frm/thread/… et: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Réponses:

105

Une solution distribuée rapide, un peu meilleure que la vôtre, mais 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 des nombres distribués non uniformes biaisés quelle que soit la qualité de rand(). Pour un test complet de la qualité de cette méthode, veuillez lire ceci .

Marque B
la source
2
Merci, cela semble être assez bon pour moi à partir de tests rapides - sa distribution pour le -1, 0, 1 est presque 33:33:33.
Matěj Zábský
3
Il renvoie toujours la valeur maximale. Est-ce que je manque quelque chose ici? : |
rohan-patel
15
rand()devrait être considéré comme dangereux en C ++, il existe de bien meilleures façons d'obtenir quelque chose qui est uniformément distribué et en fait aléatoire.
Mgetz le
1
Renvoie-t-il vraiment un nombre correct dans la plage 100% du temps? J'ai trouvé une autre réponse stackoverflow ici qui utilise la récursivité pour le faire "dans le bon sens": stackoverflow.com/a/6852396/623622
Czarek Tomczak
2
Puisqu'il s'agit d'une réponse très positive (que souhaitée), qui semble une source d'information fiable pour de nombreux nouveaux lecteurs, je pense qu'il est très important de mentionner la qualité et les dangers potentiels de cette solution, j'ai donc fait une modification.
plasmacel
297

La réponse C ++ la plus simple (et donc la meilleure) (en 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 des préjugés. Pas besoin de s'inquiéter d'utiliser le temps comme une graine aléatoire.

Walter
la source
1
De nos jours, cela devrait être la réponse . Référence de génération de nombres pseudo-aléatoires pour plus de fonctionnalités.
alextoind
8
Je suis d'accord sur le "plus simple" (et le plus idiomatique), pas sur le "meilleur". Malheureusement, la norme ne donne aucune garantie sur random_devicece qui pourrait être complètement enfreint dans certains cas . De plus, mt19937bien qu'un très bon choix d'usage général, ce n'est pas le plus rapide des générateurs de bonne qualité (voir cette comparaison ) et pourrait donc ne pas être le candidat idéal pour l'OP.
Alberto M
1
@AlbertoM Malheureusement, la comparaison à laquelle vous faites référence ne fournit pas suffisamment de détails et n'est pas reproductible, ce qui la rend douteuse (d'ailleurs, elle date de 2015, alors que ma réponse remonte à 2013). Il est peut-être vrai qu'il existe de meilleures méthodes (et, espérons-le, le minstdsera à l'avenir ), mais c'est un progrès. Quant à la mauvaise mise en œuvrerandom_device - c'est horrible et devrait être considéré comme un bogue (peut-être aussi du standard C ++, si cela le permet).
Walter
1
Je suis totalement d'accord avec vous; Je ne voulais pas vraiment critiquer votre solution en soi , je voulais juste avertir le lecteur occasionnel que la réponse définitive à ce sujet, malgré les promesses de C ++ 11, n'a pas encore été écrite. Je vais publier un aperçu du sujet à partir de 2015 en réponse à une question connexe .
Alberto M
1
C'est «le plus simple»? Pourriez-vous expliquer pourquoi le plus simple rand()n'est pas une option, et est-ce important pour une utilisation non critique, comme la génération d'un index pivot aléatoire? De plus, dois-je me soucier de la construction de random_device/ mt19937/ uniform_int_distributiondans une boucle serrée / une fonction intégrée? Dois-je préférer les faire circuler?
bluenote10
60

Si votre compilateur prend en charge C ++ 0x et que son utilisation est une option pour vous, le nouvel en- <random>tête standard répondra probablement à vos besoins. Il a une haute qualité uniform_int_distributionqui acceptera les limites minimales et maximales (incluses selon vos besoins), et vous pouvez choisir parmi divers générateurs de nombres aléatoires pour vous connecter à cette distribution.

Voici le code qui génère un million de ints aléatoires uniformément répartis dans [-57, 365]. J'ai utilisé les nouvelles <chrono>installations std pour 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 imprime:

2.10268e + 07 nombres aléatoires par seconde.

Vous pouvez amorcer le générateur en passant un int à son constructeur:

    G g(seed);

Si vous trouvez plus tard que int cela ne couvre pas la plage dont vous avez besoin pour votre distribution, cela peut être corrigé en changeant la uniform_int_distributionmême chose (par exemple en long long):

    typedef std::uniform_int_distribution<long long> D;

Si vous constatez plus tard que le minstd_randgénérateur n'est pas de qualité suffisante, il peut également être facilement remplacé. 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érateur.

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 pour tenter 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 x_préfixe fait référence à "attendu")

Howard Hinnant
la source
3
Cette réponse peut utiliser un court extrait de code récapitulatif qui affiche uniquement le code réellement nécessaire pour générer un entier aléatoire à partir d'une plage.
arekolek
Le problème est facilité par le fait que le minimum et le maximum de la distribution ne changent jamais. Et si vous deviez créer dà chaque itération avec des limites différentes? Dans quelle mesure cela ralentirait-il la boucle?
quant_dev
16

Divisons le problème en deux parties:

  • Générer un nombre aléatoire n 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 () soit parfaitement uniforme. L'utilisation de modulo ajoutera un biais aux premiers (RAND_MAX + 1) % (max-min+1)nombres. Donc , si nous pouvions changer comme par magie RAND_MAXàRAND_MAX - (RAND_MAX + 1) % (max-min+1) , il n'y aurait plus aucun parti pris.

Il s'avère que nous pouvons utiliser cette intuition si nous sommes prêts à autoriser le pseudo-non-déterminisme dans le temps d'exécution de notre algorithme. Chaque fois que rand () renvoie un nombre trop grand, nous demandons simplement un autre nombre aléatoire jusqu'à ce que nous en obtenions un qui soit assez petit.

Le temps d'exécution est maintenant géométriquement distribué , avec la valeur attendue 1/ppest la probabilité d'obtenir un nombre suffisamment petit au premier essai. Puisque RAND_MAX - (RAND_MAX + 1) % (max-min+1)est toujours inférieur à (RAND_MAX + 1) / 2, nous le savons p > 1/2, donc le nombre d'itérations attendu sera toujours inférieur à deux pour n'importe quelle plage. 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 avec cette technique.

ÉDITER:

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 cela vous-même. J'ai vu beaucoup d'implémentations d'échantillonnage de rejet et il est souvent très difficile de voir si c'est correct ou non.

Jørgen Fogh
la source
Par souci d'exhaustivité: il s'agit de l' échantillonnage par rejet .
etarion
3
Fait amusant: Joel Spolsky a mentionné une fois une version de cette question comme exemple de ce à quoi StackOverflow était capable de répondre. Je regardais à travers les réponses sur le site impliquant l' échantillonnage de rejet à ce moment - là et chaque seul un était incorrect.
Jørgen Fogh
13

Et le Mersenne Twister ? L'implémentation Boost est plutôt facile à utiliser et est bien testée dans de nombreuses applications du monde réel. 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 ne seriez pas convaincu que vous devriez l'utiliser sur le bien inférieur rand():

Le Mersenne Twister est un générateur de "nombres aléatoires" inventé par Makoto Matsumoto et Takuji Nishimura; leur site Web comprend de nombreuses implémentations de l'algorithme.

Essentiellement, le Mersenne Twister est un très grand registre à décalage à rétroaction linéaire. L'algorithme fonctionne sur une graine de 19 937 bits, stockée 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 Mersenne Twister est son utilisation d'opérations binaires - par opposition à une multiplication chronophage - 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.

Aphex
la source
1
Le twister Mersenne est un bon générateur, mais le problème auquel il est confronté demeure, quel que soit le générateur sous-jacent lui-même.
Jerry Coffin
Je ne veux pas utiliser Boost uniquement pour le générateur aléatoire, car (puisque mon projet est une bibliothèque) cela signifie introduire une autre dépendance au projet. Je serai probablement obligé de l'utiliser de toute façon à l'avenir, alors je pourrai passer à ce générateur.
Matěj Zábský
1
@Jerry Coffin Quel problème? Je l'ai proposé car il répondait à toutes ses exigences: il est rapide, il est uniforme (en utilisant la boost::uniform_intdistribution), vous pouvez transformer les gammes min max en ce que vous voulez, et c'est semable.
Aphex
@mzabsky Je ne laisserais probablement pas cela m'arrêter, quand j'ai dû envoyer mes projets à mes professeurs pour soumission, j'ai simplement inclus les fichiers d'en-tête boost pertinents que j'utilisais; vous ne devriez pas avoir à empaqueter toute la bibliothèque de boost de 40 Mo avec votre code. Bien sûr, dans votre cas, cela pourrait ne pas être possible pour d'autres raisons telles que le droit d'auteur ...
Aphex
@Aphex Mon projet n'est pas vraiment un simulateur scientifique ou quelque chose qui nécessite une distribution vraiment uniforme. J'ai utilisé l'ancien générateur pendant 1,5 an sans aucun problème, je n'ai remarqué la distribution biaisée que lorsque j'en ai eu besoin pour la première fois pour générer des nombres à partir de très petites plages (3 dans ce cas). La vitesse reste cependant un argument pour envisager la solution boost. Je vais regarder dans sa licence pour voir si je peux simplement ajouter les quelques fichiers nécessaires à mon projet - j'aime le "Checkout -> F5 -> ready to use" tel qu'il est maintenant.
Matěj Zábský
11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Il s'agit d'un mappage de 32768 entiers en (nMax-nMin + 1) entiers. Le mappage sera assez bon si (nMax-nMin + 1) est petit (comme dans votre exigence). Notez cependant que si (nMax-nMin + 1) est grand, le mappage ne fonctionnera pas (par exemple, vous ne pouvez pas mapper 32768 valeurs à 30000 valeurs avec une probabilité égale). 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 rand () qui sont hors limites.

Lior Kogan
la source
Malgré son impopularité, c'est aussi ce que j'utilise pour mes projets non scientifiques. Facile à comprendre (vous n'avez pas besoin d'un diplôme en mathématiques) et fonctionne correctement (jamais eu à profiler un code en l'utilisant). :) Dans le cas de grandes plages, je suppose que nous pourrions enchaîner deux valeurs rand () et obtenir une valeur de 30 bits avec laquelle travailler (en supposant RAND_MAX = 0x7fff, soit 15 bits aléatoires)
efotinis
changez RAND_MAXen (double) RAND_MAXpour éviter l'avertissement de dépassement d'entier.
alex
4

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 raisonnablement petite, il n'y a aucune raison de mettre en cache le côté droit de la comparaison dans la doboucle.

Jérémie Willcock
la source
OMI, aucune des solutions présentées il n'y a vraiment beaucoup d'amélioration. Sa solution basée sur la boucle fonctionne, mais sera probablement assez inefficace, en particulier pour une petite plage comme le décrit l'OP. Sa solution uniforme dévient ne produit pas réellement uniforme se écarte du tout. Tout au plus, cela camoufle le manque d'uniformité.
Jerry Coffin
@Jerry: Veuillez vérifier la nouvelle version.
Jeremiah Willcock
Je ne suis pas certain que cela fonctionne correctement. C'est possible, mais l'exactitude ne me semble pas évidente, du moins pour moi.
Jerry Coffin
@Jerry: Voici mon raisonnement: supposons que la plage soit [0, h)pour la simplicité. L'appel rand()a RAND_MAX + 1des valeurs de retour possibles; prenant rand() % heffondrements (RAND_MAX + 1) / hd'entre eux à chacune des hvaleurs de sortie, à l' exception que (RAND_MAX + 1) / h + 1d'entre eux sont mis en correspondance avec les valeurs qui sont inférieures (RAND_MAX + 1) % h( à cause de la dernière période partielle à travers les hsorties). Nous supprimons donc (RAND_MAX + 1) % hles sorties possibles pour obtenir une distribution non biaisée.
Jeremiah Willcock
3

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 que vous voulez, et dans les scénarios non cryptographiques, elle peut en fait surpasser une implémentation typique de la bibliothèque C.

DSimon
la source
1

supposons que min et max sont des valeurs int, [et] signifie inclure cette valeur, (et) signifie ne pas inclure cette valeur, en utilisant ci-dessus pour obtenir la bonne valeur en utilisant c ++ rand ()

référence: pour () [] définir, visitez:

https://en.wikipedia.org/wiki/Interval_(mathematics)

pour la fonction rand et srand ou la définition RAND_MAX, 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
Huang Kun
la source
0

Dans ce thread, l'échantillonnage de rejet a déjà été discuté, mais je voulais suggérer une optimisation basée sur le fait qu'elle rand() % 2^somethingn'introduit aucun biais comme déjà mentionné ci-dessus.

L'algorithme est vraiment simple:

  • calculer la plus petite puissance de 2 supérieure à la longueur de l'intervalle
  • randomiser un nombre dans ce "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, et donc le nombre d'échecs sera plus petit.

PS
Évidemment, éviter la récursivité serait plus efficace (pas besoin de calculer au-delà du plafond du journal ..) mais j'ai pensé que c'était plus lisible pour cet exemple.

Pado
la source
0

Notez que dans la plupart des suggestions, la valeur aléatoire initiale que vous avez obtenue de la fonction rand (), qui est généralement de 0 à RAND_MAX, est simplement gaspillée. Vous n'en créez qu'un seul nombre aléatoire, alors qu'il existe une procédure sonore qui peut vous en donner plus.

Supposons que vous vouliez une région [min, max] de nombres aléatoires entiers. Nous partons de [0, max-min]

Prendre la base b = max-min + 1

Commencez par représenter un nombre obtenu de rand () en base b.

De cette façon, vous avez floor (log (b, RAND_MAX)) parce que chaque chiffre de la base b, sauf peut-être le dernier, représente un nombre aléatoire dans la plage [0, max-min].

Bien sûr, le décalage final vers [min, max] est simple pour chaque nombre aléatoire r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Si NUM_DIGIT est le nombre de chiffres en base b que vous pouvez extraire et c'est

NUM_DIGIT = floor(log(b,RAND_MAX))

alors ce qui précède est une implémentation simple d'extraction de NUM_DIGIT nombres aléatoires de 0 à b-1 à partir d'un nombre aléatoire RAND_MAX fournissant b <RAND_MAX.

alex.peter
la source
-1

La formule pour cela est très simple, alors essayez cette expression,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Sohail xIN3N
la source
2
Tout le problème était d'utiliser le rand de C / C ++ qui renvoie un entier dans une plage spécifiée par le runtime. Comme démontré dans ce fil, le mappage d'entiers aléatoires de [0, RAND_MAX] à [MIN, MAX] n'est pas tout à fait simple, si vous voulez éviter de détruire leurs propriétés statistiques ou leurs performances. Si vous avez des doubles dans la plage [0, 1], le mappage est facile.
Matěj Zábský
2
Votre réponse est fausse, vous devriez utiliser le module à la place:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes
-2

L'expression suivante doit être impartiale si je ne me trompe pas:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Je suppose ici que rand () vous donne une valeur aléatoire comprise entre 0,0 et 1,0 NON compris 1,0 et que max et min sont des entiers avec la condition que min <max.

Moritz
la source
std::floorrenvoie double, et nous avons besoin d'une valeur entière ici. Je voudrais simplement lancer au intlieu d'utiliser std::floor.
musiphil