117 votes

Générer des nombres aléatoires de manière uniforme sur toute une plage de valeurs

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

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

Actuellement, je génère en tant que :

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

D'après mes tests, les numéros aléatoires sont générés autour d'un seul point.

Example
min = 3604607;
max = 7654607;

Numéros aléatoires générés :

3631594
3609293
3630000
3628441
3636376
3621404

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

4 votes

Construire un Dice-O-Matic : gamesbyemail.com/News/DiceOMatic

1 votes

Je ne savais pas que le C++ rand() était uniforme. Quelle bibliothèque utilisez-vous ? cstdlib.h 's rand() n'est PAS uniforme : cplusplus.com/reference/cstdlib/rand

3 votes

Non, rand() est uniforme (sauf dans certaines implémentations précoces et boguées). Ce qui n'est pas uniforme, c'est l'utilisation de l'opérateur modulo '%' pour restreindre la plage. Voir stackoverflow.com/questions/2999075/ pour une solution appropriée, ou si vous disposez de 'arc4random_uniform', vous pouvez également l'utiliser directement.

213voto

Jefffrey Points 31698

Pourquoi rand est une mauvaise idée

La plupart des réponses que vous avez obtenues ici utilisent l'expression rand et l'opérateur de module. Cette méthode peut ne pas générer les nombres de manière uniforme (cela dépend de la plage et de la valeur de l'option RAND_MAX ), et est donc déconseillé.

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

Avec C++11, de multiples autres options sont apparues. L'une d'entre elles répond très bien à votre besoin de générer un nombre aléatoire dans un intervalle : 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 aquí C'est l'exemple à suivre.

La fonction de modèle peut aider certains :

template<typename T>
T random(T range_from, T range_to) {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<T>    distr(range_from, range_to);
    return distr(generator);
}

Autres générateurs aléatoires

El <random> en-tête propose d'innombrables autres générateurs de nombres aléatoires avec différents types de distributions, notamment Bernoulli, Poisson et normale.

Comment puis-je mélanger un conteneur ?

La norme prévoit 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éordonnera les éléments de manière aléatoire, avec une complexité linéaire.

Boost.Random

Une autre alternative, dans le cas où vous n'avez pas accès à un compilateur C++11+, est d'utiliser Boost.Random . Son interface est très similaire à celle du C++11.

34 votes

Prêtez attention à cette réponse, car elle est beaucoup plus moderne.

0 votes

Ce site est la bonne réponse. Merci ! Cependant, j'aimerais voir une description plus approfondie de chaque étape de ce code. Par exemple, qu'est-ce qu'un mt19937 type ?

0 votes

@Apollo La documentation indique "32-bit Mersenne Twister by Matsumoto and Nishimura, 1998". Je suppose que c'est un algorithme pour générer des nombres pseudo-aléatoires.

64voto

peterchen Points 21792

[modifier] Avertissement : Ne pas utiliser rand() pour les statistiques, la simulation, la cryptographie ou tout autre sujet sérieux.

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

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


En général, les bits de poids fort présentent une meilleure distribution que les bits de poids faible, de sorte que 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 : s'assurer que RAND_MAX+1 ne déborde pas (merci Demi) !

La division génère un nombre aléatoire dans l'intervalle [0, 1] ; "étirez" celui-ci jusqu'à la plage requise. Ce n'est que lorsque max-min+1 se rapproche de RAND_MAX que vous avez besoin d'une fonction "BigRand()" comme celle proposée par Mark Ransom.

Cela permet également d'éviter certains problèmes de découpage dus au modulo, qui peuvent détériorer encore plus vos chiffres.


Le générateur de nombres aléatoires intégré n'est pas garanti d'avoir la qualité requise pour les simulations statistiques. Il est acceptable que les nombres "semblent aléatoires" pour un humain, mais pour une application sérieuse, vous devriez prendre quelque chose de mieux - ou au moins vérifier ses propriétés (une distribution uniforme est généralement bonne, mais les valeurs ont tendance à se corréler, et la séquence est déterministe). Knuth a rédigé un excellent traité (bien que difficile à lire) sur les générateurs de nombres aléatoires, et j'ai récemment découvert que LFSR est excellent et très simple à mettre en œuvre, si ses propriétés vous conviennent.

5 votes

BigRand peut donner de meilleurs résultats même lorsque l'intervalle souhaité ne dépasse pas RAND_MAX. Considérez le cas où RAND_MAX est 32767 et que vous voulez 32767 valeurs possibles - deux de ces 32768 nombres aléatoires (y compris zéro) vont correspondre à la même sortie, et auront deux fois plus de chances de se produire que les autres. Ce n'est pas une propriété aléatoire idéale !

0 votes

@Mark : Vous avez raison, bon exemple. Néanmoins, si c'est un vrai problème, j'éviterais le intégré. rand() complètement, puisque la norme ne comporte aucune garantie de qualité.

10 votes

(RAND_MAX + 1) est une mauvaise idée. Cela peut se retourner contre vous et vous donner une valeur négative. Il est préférable de faire quelque chose comme : ((double)RAND_MAX) + 1.0

10voto

Mark Ransom Points 132545

Si RAND_MAX est de 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();
}

1 votes

Je ne pense pas que cela fonctionne. Les générateurs de nombres pseudo-aléatoires sont généralement déterministes. Par exemple, si d'abord rand les retours d'appels 0x1234 et le second 0x5678 alors vous obtenez 0x12345678 . C'est le sólo numéro que vous pouvez obtenir qui commence par 0x1234 car le prochain numéro sera toujours 0x5678 . Vous obtenez des résultats sur 32 bits, mais vous n'avez que 32768 nombres possibles.

1 votes

@user694733 un bon générateur de nombres aléatoires a une période qui est plus grande que le nombre de sorties qu'il peut générer, donc 0x1234 ne le fera pas. siempre être suivi de 0x5678.

8voto

Jeff Thomas Points 183

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.

0 votes

J'ai fait un peu de travail sur uniform_int avec un twister merseinne et malheureusement pour certaines plages les valeurs retournées par uniform_int ne sont pas aussi uniformes que je l'attendais. Par exemple, uniform_int<>( 0, 3 ) a tendance à produire plus de 0 que de 1 ou de 2.

0 votes

@ScaryAardvark cela ressemble à une mauvaise implémentation de uniform_int alors. Il est assez facile de générer une sortie non biaisée, il y a eu plusieurs questions ici qui démontrent la méthode.

0 votes

@Mark Ransom. Oui, je suis tout à fait d'accord.

8voto

SoapBox Points 14183

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

Vous pouvez également écrire le vôtre à l'aide d'un algorithme de cryptage (tel que AES ). En choisissant une graine et un IV puis en rechiffrant continuellement la sortie de la fonction de chiffrement. Utiliser OpenSSL est plus facile, mais moins viril.

0 votes

Je ne peux pas utiliser de bibliothèque tierce Je suis limité au C++ uniquement.

0 votes

Ensuite, prenez la voie virile, implémentez AES ou un autre algorithme de cryptage.

2 votes

Le code RC4 est trivial et suffisamment aléatoire pour tous les usages pratiques (à l'exception du WEP, mais ce n'est pas entièrement la faute de RC4). Je le pense, c'est un code incroyablement trivial. Une vingtaine de lignes, à peu près. L'entrée Wikipedia a un pseudo-code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X