web-dev-qa-db-fra.com

Générer des nombres aléatoires uniformément sur une plage entière

Je dois générer des nombres aléatoires dans un intervalle spécifié, [max; min].

De plus, les nombres aléatoires doivent être uniformément répartis sur l'intervalle et non situés à un point particulier.

Currenly je génère comme:

for(int i=0; i<6; i++)
{
    DWORD random = Rand()%(max-min+1) + min;
}

De mes tests, des nombres aléatoires sont générés autour d'un point seulement.

Example
min = 3604607;
max = 7654607;

Nombres aléatoires générés:

3631594
3609293
3630000
3628441
3636376
3621404

Des réponses ci-dessous: OK, Rand_MAX est 32767. Je suis sur la plate-forme C++ Windows. Existe-t-il une autre méthode pour générer des nombres aléatoires avec une distribution uniforme?

81
anand

Pourquoi Rand est une mauvaise idée

La plupart des réponses que vous avez obtenues ici utilisent la fonction Rand et l'opérateur de module. Cette méthode ne peut pas générer des nombres de manière uniforme (elle dépend de la plage et de la valeur de Rand_MAX), et est donc découragé.

C++ 11 et génération sur une plage

Avec C++ 11, plusieurs autres options se sont présentées. L’un d’eux qui répond à vos besoins, pour générer un nombre aléatoire dans une plage, est très joli: std::uniform_int_distribution . Voici un exemple:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  Rand_dev;
std::mt19937                        generator(Rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

Et ici est l'exemple en cours.

Autres générateurs aléatoires

Le <random> header offre d'innombrables autres générateurs de nombres aléatoires avec différents types de distributions, dont Bernoulli, Poisson et normal.

Comment puis-je mélanger un conteneur?

La norme fournit std::shuffle , qui peut être utilisé comme suit:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

L'algorithme réorganisera les éléments de manière aléatoire, avec une complexité linéaire.

Boost.Random

Une autre alternative, au cas où vous n’auriez pas accès à un compilateur C++ 11 +, consiste à utiliser Boost.Random . Son interface est très similaire à celle de C++ 11.

132
Shoe

[edit] Avertissement: n'utilisez pas Rand() pour les statistiques, la simulation, la cryptographie ou quoi que ce soit. grave.

C'est assez bon pour faire des nombres regardez aléatoire pour un humain typique pressé, pas plus.

Voir réponse de @ Jefffrey pour de meilleures options, ou cette réponse pour les nombres aléatoires crypto-sécurisés.


En règle générale, les bits hauts présentent une meilleure distribution que les bits bas. La méthode recommandée pour générer des nombres aléatoires d'une plage à des fins simples est la suivante:

((double) Rand() / (Rand_MAX+1)) * (max-min+1) + min

Note : assurez-vous que Rand_MAX + 1 ne déborde pas (merci Demi)!

La division génère un nombre aléatoire dans l'intervalle [0, 1); "étirer" ceci à la gamme requise. Ce n'est que lorsque max-min + 1 se rapproche de Rand_MAX que vous avez besoin d'une fonction "BigRand ()" similaire à celle publiée par Mark Ransom.

Cela évite également certains problèmes de découpage dus au modulo, ce qui peut aggraver encore plus vos chiffres.


Le générateur de nombres aléatoires intégré ne garantit pas la qualité requise pour les simulations statistiques. Il est normal que les nombres "semblent aléatoires" pour un humain, mais pour une application sérieuse, vous devez prendre quelque chose de mieux - ou du moins en vérifier les propriétés (une distribution uniforme est généralement bonne, mais les valeurs tendent à être corrélées et la séquence est déterministe. ). Knuth a un excellent traité (même s'il est difficile à lire) sur les générateurs de nombres aléatoires, et j'ai récemment découvert que LFSR était excellent et extrêmement simple à mettre en œuvre, étant donné ses propriétés sont OK pour vous.

58
peterchen

J'aimerais compléter les excellentes réponses d'Angry Shoe et de peterchen avec un bref aperçu de l'état de la technique en 2015:

Quelques bons choix

randutils

La bibliothèque randutils(présentation) est une nouveauté intéressante, offrant une interface simple et des capacités aléatoires robustes (déclarées). Cela a pour inconvénient d'ajouter une dépendance à votre projet et, étant nouveau, il n'a pas été testé de manière approfondie. Quoi qu'il en soit, étant libre (licence MIT) et en-tête uniquement, je pense que cela vaut la peine d'essayer.

Échantillon minimal: un jet de dé

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Même si la bibliothèque n’est pas intéressée, le site Web ( http://www.pcg-random.org/ ) propose de nombreux articles intéressants sur le thème de la génération de nombres aléatoires en général et de la bibliothèque C++. en particulier.

Boost.Random

Boost.Random (documentation) est la bibliothèque qui a inspiré le <random> De C++ 11, avec qui partage une grande partie de l'interface. Bien que théoriquement aussi une dépendance externe, Boost a maintenant le statut de bibliothèque "quasi standard", et son module Random pourrait être considéré comme le choix classique pour une génération de nombres aléatoires de bonne qualité. Il présente deux avantages par rapport à la solution C++ 11:

  • il est plus portable, nécessitant juste le support du compilateur pour C++ 03
  • son random_device utilise des méthodes spécifiques au système pour offrir un ensemencement de bonne qualité

Le seul petit défaut est que le module offrant random_device N'est pas seulement en-tête, il faut compiler et lier boost_random.

Échantillon minimal: un jet de dé

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  Rand_dev;
    boost::random::mt19937                        generator(Rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Bien que l'échantillon minimal fonctionne bien, les programmes réels devraient utiliser une paire d'améliorations:

  • make mt19937 a thread_local: le générateur est assez rond (> 2 Ko) et mieux vaut ne pas être alloué sur la pile
  • graine mt19937 avec plus d'un entier: le Mersenne Twister a un grand état et peut tirer profit de plus d'entropie lors de l'initialisation

Certains choix pas très bons

La bibliothèque C++ 11

Bien qu’étant la solution la plus idiomatique, la bibliothèque <random> N’offre pas grand chose en échange de la complexité de son interface, même pour les besoins de base. La faille est dans std::random_device: La norme n’impose aucune qualité minimale pour sa sortie (tant que entropy() renvoie 0) Et, à compter de 2015, MinGW (non le compilateur le plus utilisé, mais à peine un choix ésotérique) imprimera toujours 4 sur l’échantillon minimal.

Échantillon minimal: un jet de dé

#include <iostream>
#include <random>
int main() {
    std::random_device                  Rand_dev;
    std::mt19937                        generator(Rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Si la mise en œuvre n'est pas pourrie, cette solution doit être équivalente à celle de Boost, et les mêmes suggestions s'appliquent.

La solution de Godot

Échantillon minimal: un jet de dé

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

C'est une solution simple, efficace et soignée. Seul défaut, la compilation prendra du temps - environ deux ans, à condition que C++ 17 soit publié à temps et que la fonction expérimentale randint soit approuvée dans la nouvelle norme. Peut-être qu’à ce moment-là aussi les garanties sur la qualité des semences s’amélioreront.

La solution pire-est-mieux

Échantillon minimal: un jet de dé

#include <cstdlib>
#include <ctime>
#include <iostream>

    int main() {
        std::srand(std::time(nullptr));
        std::cout << (std::Rand() % 6 + 1);
    }

L'ancienne solution C est considérée comme nuisible et pour de bonnes raisons (voir les autres réponses ici ou cette analyse détaillée ). Néanmoins, il a ses avantages: il est simple, portable, rapide et honnête, en ce sens qu’on sait que les nombres aléatoires que l’on obtient ne sont pas décents, et qu’on n’est donc pas tenté de les utiliser à des fins sérieuses.

La solution troll comptable

Échantillon minimal: un jet de dé

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Bien que 9 soit un résultat quelque peu inhabituel pour un jet de dés régulier, il faut admirer l'excellente combinaison de qualités de cette solution, qui réussit à être la plus rapide, la plus simple, la plus conviviale pour les caches et la plus portable. En remplaçant 9 par 4, on obtient un générateur parfait pour tout type de Donjons et Dragons, tout en évitant les valeurs 1, 2 et 3, chargées de symboles. ce programme engendre réellement un comportement indéfini.

14
Alberto M

Si Rand_MAX est 32767, vous pouvez facilement doubler le nombre de bits.

int BigRand()
{
    assert(INT_MAX/(Rand_MAX+1) > Rand_MAX);
    return Rand() * (Rand_MAX+1) + Rand();
}
11
Mark Ransom

Si vous le pouvez, utilisez Boost . J'ai eu de la chance avec leur bibliothèque aléatoire .

uniform_int devrait faire ce que vous voulez.

9
Jeff Thomas

Si vous êtes préoccupé par le caractère aléatoire et non par la vitesse, utilisez une méthode sécurisée de génération de nombres aléatoires. Il y a plusieurs façons de faire cela ... La plus simple étant d'utiliser OpenSSLGénérateur de nombres aléatoires .

Vous pouvez également écrire le vôtre en utilisant un algorithme de chiffrement (comme AES ). En choisissant une graine et un IV , puis en rechiffrant en permanence la sortie de la fonction de chiffrement. Utiliser OpenSSL est plus facile, mais moins viril.

8
SoapBox

Vous devriez regarder Rand_MAX pour votre compilateur/environnement particulier. Je pense que vous verriez ces résultats si Rand () produit un nombre aléatoire de 16 bits. (vous semblez supposer que ce sera un nombre de 32 bits).

Je ne peux pas vous promettre que c'est la solution, mais signalez votre valeur de Rand_MAX et donnez un peu plus de détails sur votre environnement.

5
abelenky

Vérifiez ce que Rand_MAX est sur votre système - je suppose qu'il est seulement 16 bits, et votre portée est trop grande pour cela.

Au-delà, voir cette discussion sur: Génération d’entiers aléatoires dans une plage souhaitée et les remarques sur l’utilisation (ou non) de la fonction C Rand () .

3
Rob Walker

Ce n'est pas le code, mais cette logique peut vous aider.

static double rnd(void)
{
return (1.0/(Rand_MAX+1.0)*((double)(Rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
register int i;
srand( seed );
for( i=0; i<POOLSIZE; i++){
pool[i]= rnd();
}
}

 static double rnd0_1(void)
 {  // This function returns a number between 0 and 1
static int i=POOLSIZE-1;
double r;

i = (int)(POOLSIZE*pool[i]);
r=pool[i];
pool[i]=rnd();
return (r);
}
2
user3503711

Si vous souhaitez que les nombres soient répartis uniformément sur la plage, vous devez la diviser en plusieurs sections égales représentant le nombre de points dont vous avez besoin. Ensuite, obtenez un nombre aléatoire avec un min/max pour chaque section.

Autre remarque, vous ne devriez probablement pas utiliser Rand () car il n’est pas très bon pour générer des nombres aléatoires. Je ne sais pas sur quelle plate-forme vous utilisez, mais il y a probablement une meilleure fonction que vous pouvez appeler comme random ().

2
Jason Coco

Cela devrait fournir une distribution uniforme sur toute la gamme [low, high) sans utiliser de float, tant que la plage globale est inférieure à Rand_MAX.

uint32_t Rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= Rand_MAX
    assert(low < high);
    assert(high - low <= Rand_MAX);

    uint32_t range = high-low;
    uint32_t scale = Rand_MAX/range;
    do {
        val = Rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

et pour des valeurs supérieures à Rand_MAX, vous voulez quelque chose comme

uint32_t Rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < Rand_MAX)
        return Rand_range_low(low, high);
    uint32_t scale = range/Rand_MAX;
    do {
        val = Rand() + Rand_range(0, scale) * Rand_MAX; // scale the initial range in Rand_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

C'est à peu près comment std :: uniform_int_distribution fait les choses.

1
benf

De par leur nature, un petit échantillon de nombres aléatoires n'a pas à être uniformément distribué. Ils sont aléatoires, après tout. Je conviens que si un générateur de nombres aléatoires génère des nombres qui semblent toujours être groupés, il y a probablement un problème.

Mais gardez à l'esprit que le hasard n'est pas nécessairement uniforme.

Edit: J'ai ajouté "petit échantillon" pour clarifier.

0
Kluge

La solution donnée par man 3 Rand pour un nombre compris entre 1 et 10 inclus est:

j = 1 + (int) (10.0 * (Rand() / (Rand_MAX + 1.0)));

Dans votre cas, ce serait:

j = min + (int) ((max-min+1) * (Rand() / (Rand_MAX + 1.0)));

Bien sûr, ce n'est pas un hasard ou une uniformité parfaite, comme le signalent d'autres messages, mais cela suffit dans la plupart des cas.

0
calandoa

Bien entendu, le code suivant ne vous donnera pas de nombres aléatoires, mais pseudo-nombres aléatoires. Utilisez le code suivant

#define QUICK_Rand(m,n) m + ( std::Rand() % ( (n) - (m) + 1 ) )

Par exemple:

int myRand = QUICK_Rand(10, 20);

Vous devez appeler

srand(time(0));  // Initialize random number generator.

sinon les nombres ne seront pas presque aléatoires.

0
Michael Haephrati

@ Solution ((double) Rand() / (Rand_MAX+1)) * (max-min+1) + min

Attention : N'oubliez pas, en raison des erreurs d'étirement et de précision (même si Rand_MAX était suffisamment grand), vous ne pourrez générer que des données distribuées de manière uniforme. "bacs" et pas tous les nombres dans [min, max].


@ Solution: Bigrand

Avertissement : Notez que ceci double les bits, mais ne permet toujours pas de générer tous les nombres de votre plage en général, c'est-à-dire qu'il ne s'agit pas nécessairement true que BigRand () générera tous les nombres compris dans sa plage.


Info : votre approche (modulo) est "correcte" tant que la plage de Rand () dépasse votre plage d'intervalle et que Rand () est "uniforme". . L'erreur pour les premiers nombres max-min au plus est 1/(Rand_MAX +1).

Aussi, je suggère de basculer vers le nouveau package aléatoire e en C++ 11 aussi, qui offre de meilleures et plus nombreuses variétés d’implémentations que Rand ().

0
Ritualmaster