Ceci est la suite d'une question précédemment publiée:
Comment générer un nombre aléatoire en C?
Je souhaite pouvoir générer un nombre aléatoire à partir d'une plage particulière, telle que 1 à 6, pour imiter les côtés d'un dé.
Comment pourrais-je procéder?
Réponses:
Toutes les réponses à ce jour sont mathématiquement fausses. Le retour
rand() % N
ne donne pas uniformément un nombre dans la plage à[0, N)
moins deN
diviser la longueur de l'intervalle dans lequelrand()
renvoie (c'est-à-dire une puissance de 2). De plus, on ne sait pas si les modules derand()
sont indépendants: il est possible qu'ils disparaissent0, 1, 2, ...
, ce qui est uniforme mais pas très aléatoire. La seule hypothèse qu'il semble raisonnable de faire est celle quirand()
émet une distribution de Poisson: deux sous-intervalles non chevauchants de même taille sont également probables et indépendants. Pour un ensemble fini de valeurs, cela implique une distribution uniforme et garantit également que les valeurs derand()
sont bien dispersées.Cela signifie que la seule façon correcte de modifier la plage de
rand()
est de la diviser en cases; par exemple, siRAND_MAX == 11
et vous voulez une plage de1..6
, vous devez attribuer{0,1}
à 1,{2,3}
à 2, et ainsi de suite. Ce sont des intervalles disjoints, de taille égale et donc distribués uniformément et indépendamment.La suggestion d'utiliser la division en virgule flottante est mathématiquement plausible mais souffre en principe de problèmes d'arrondi. La
double
précision est peut -être suffisamment élevée pour que cela fonctionne; peut-être pas. Je ne sais pas et je ne veux pas avoir à le comprendre; dans tous les cas, la réponse dépend du système.La bonne façon est d'utiliser l'arithmétique des nombres entiers. Autrement dit, vous voulez quelque chose comme ce qui suit:
La boucle est nécessaire pour obtenir une distribution parfaitement uniforme. Par exemple, si vous recevez des nombres aléatoires de 0 à 2 et que vous ne voulez que des nombres de 0 à 1, vous continuez à tirer jusqu'à ce que vous n'obteniez pas un 2; il n'est pas difficile de vérifier que cela donne 0 ou 1 avec une probabilité égale. Cette méthode est également décrite dans le lien que nos a donné dans leur réponse, bien que codée différemment. J'utilise
random()
plutôt querand()
car il a une meilleure distribution (comme indiqué par la page de manuel pourrand()
).Si vous souhaitez obtenir des valeurs aléatoires en dehors de la plage par défaut
[0, RAND_MAX]
, vous devez faire quelque chose de délicat. Le plus opportun est peut-être de définir une fonctionrandom_extended()
qui extrait lesn
bits (en utilisantrandom_at_most()
) et les retourne[0, 2**n)
, puis appliquezrandom_at_most()
avecrandom_extended()
à la place derandom()
(et2**n - 1
à la place deRAND_MAX
) pour extraire une valeur aléatoire inférieure à2**n
, en supposant que vous avez un type numérique qui peut contenir un tel une valeur. Enfin, bien sûr, vous pouvez obtenir des valeurs en[min, max]
utilisantmin + random_at_most(max - min)
, y compris des valeurs négatives.la source
max - min > RAND_MAX
, ce qui est plus grave que le problème que j'ai indiqué ci-dessus (par exemple, VC ++RAND_MAX
n'en a que 32767).do {} while()
.Suite à la réponse de @Ryan Reich, j'ai pensé proposer ma version nettoyée. La première vérification des limites n'est pas requise étant donné la deuxième vérification des limites, et je l'ai rendue itérative plutôt que récursive. Il renvoie des valeurs dans la plage [min, max], où
max >= min
et1+max-min < RAND_MAX
.la source
limit
un int (et éventuellementbucket
aussi) puisqueRAND_MAX / range
<INT_MAX
etbuckets * range
<=RAND_MAX
. EDIT: J'ai soumis et édité la proposition.Voici une formule si vous connaissez les valeurs max et min d'une plage et que vous souhaitez générer des nombres compris entre la plage:
la source
int
débordement potentiel avecmax+1-min
.Voir ici pour d'autres options.
la source
(((max-min+1)*rand())/RAND_MAX)+min
et obtenir probablement exactement la même distribution (en supposant que RAND_MAX est suffisamment petit par rapport à int pour ne pas déborder).max + 1
, si l'unrand() == RAND_MAX
ou l' autre , ourand()
est très proche,RAND_MAX
et des erreurs en virgule flottante repoussent le résultat finalmax + 1
. Pour être sûr, vous devez vérifier que le résultat est dans la plage avant de le renvoyer.RAND_MAX + 1.0
. Je ne suis toujours pas sûr que ce soit suffisant pour empêcher unmax + 1
retour: en particulier, le+ min
à la fin implique un tour qui pourrait finir par produiremax + 1
pour de grandes valeurs de rand (). Il est plus sûr d'abandonner complètement cette approche et d'utiliser l'arithmétique des nombres entiers.RAND_MAX
est remplacé parRAND_MAX+1.0
comme Christoph suggère, je crois que cela est sans danger à condition que l'+ min
on fait en utilisant l' arithmétique entier:return (unsigned int)((max - min + 1) * scaled) + min
. La raison (non évidente) est que, en supposant l'arithmétique IEEE 754 et arrondi demi-pair, (et aussi celamax - min + 1
est exactement représentable comme un double, mais ce sera vrai sur une machine typique), il est toujours vrai quex * scaled < x
pour tout double positifx
et tout doublescaled
satisfaisant0.0 <= scaled && scaled < 1.0
.randr(0, UINT_MAX)
: génère toujours 0.Ne feriez-vous pas simplement:
%
est l'opérateur de module. Essentiellement, il divise simplement par 6 et renvoie le reste ... de 0 à 5la source
rand()
inclut les bits de poids faible de l'état du générateur (s'il utilise un LCG). Je n'en ai pas vu jusqu'à présent - tous (oui, y compris MSVC avec RAND_MAX étant juste 32767) suppriment les bits de poids faible. L'utilisation du module n'est pas recommandée pour d'autres raisons, à savoir qu'elle fausse la distribution en faveur de nombres plus petits.Pour ceux qui comprennent le problème du biais mais ne supportent pas le temps d'exécution imprévisible des méthodes basées sur le rejet, cette série produit un entier aléatoire progressivement moins biaisé dans l'
[0, n-1]
intervalle:Pour ce faire, il synthétise un nombre aléatoire de
i * log_2(RAND_MAX + 1)
bits à virgule fixe de haute précision (oùi
est le nombre d'itérations) et effectue une longue multiplication parn
.Lorsque le nombre de bits est suffisamment grand par rapport à
n
, la polarisation devient incommensurablement petite.Peu importe si
RAND_MAX + 1
est inférieur àn
(comme dans cette question ), ou si ce n'est pas une puissance de deux, mais il faut veiller à éviter un débordement d'entier siRAND_MAX * n
est grand.la source
RAND_MAX
est souventINT_MAX
, doncRAND_MAX + 1
-> UB (comme INT_MIN)RAND_MAX * n
est grand". Vous devez vous organiser pour utiliser les types appropriés à vos besoins.RAND_MAX
est souventINT_MAX
" Oui, mais uniquement sur les systèmes 16 bits! Toute architecture raisonnablement moderne se situeraINT_MAX
à 2 ^ 32/2 etRAND_MAX
à 2 ^ 16 / 2. Est-ce une hypothèse incorrecte?int
compilateurs 32 bits , j'ai trouvéRAND_MAX == 32767
sur l'un etRAND_MAX == 2147483647
sur l'autre. Mon expérience globale (des décennies) est queRAND_MAX == INT_MAX
plus souvent. Donc pas d'accord qu'une architecture 32 bits raisonnablement moderne aura certainement unRAND_MAX
at2^16 / 2
. Puisque la spécification C le permet32767 <= RAND_MAX <= INT_MAX
, je code de toute façon plutôt qu'une tendance.Afin d'éviter le biais modulo (suggéré dans d'autres réponses), vous pouvez toujours utiliser:
Où "MAX" est la limite supérieure et "MIN" est la limite inférieure. Par exemple, pour les nombres entre 10 et 20:
Solution simple et meilleure que d'utiliser "rand ()% N".
la source
#include <bsd/stdlib.h>
abord. Aussi, une idée de comment obtenir cela sur Windows sans MinGW ou CygWin?Voici un algorithme légèrement plus simple que la solution de Ryan Reich:
la source
RAND_MAX + 1
peut facilement déborder d'int
addition. Dans ce cas,(RAND_MAX + 1) % range
générera des résultats douteux. Considérer(RAND_MAX + (uint32_t)1)
Bien que Ryan ait raison, la solution peut être beaucoup plus simple en fonction de ce que l'on sait de la source du caractère aléatoire. Pour reformuler le problème:
[0, MAX)
avec une distribution uniforme.[rmin, rmax]
où0 <= rmin < rmax < MAX
.D'après mon expérience, si le nombre de bacs (ou «boîtes») est significativement plus petit que la plage des nombres d'origine, et que la source d'origine est cryptographiquement forte - il n'est pas nécessaire de passer par tout ce rigamarole, et une simple division modulo le ferait suffisent (comme
output = rnd.next() % (rmax+1)
, sirmin == 0
), et produisent des nombres aléatoires qui sont distribués uniformément "assez", et sans aucune perte de vitesse. Le facteur clé est la source aléatoire (c.-à-d. Les enfants, n'essayez pas cela à la maison avecrand()
).Voici un exemple / preuve de son fonctionnement dans la pratique. Je voulais générer des nombres aléatoires de 1 à 22, ayant une source cryptographiquement forte produisant des octets aléatoires (basé sur Intel RDRAND). Les résultats sont:
C'est aussi proche de l'uniformité que nécessaire pour mon objectif (lancer de dés équitables, générer des livres de codes cryptographiquement forts pour les machines de chiffrement de la Seconde Guerre mondiale telles que http://users.telenet.be/d.rijmenants/en/kl-7sim.htm , etc. ). La sortie ne montre aucun biais appréciable.
Voici la source du générateur de nombres aléatoires (vrais) forts cryptographiquement: Intel Digital Random Number Generator et un exemple de code qui produit des nombres aléatoires 64 bits (non signés).
Je l'ai compilé sur Mac OS X avec clang-6.0.1 (directement), et avec gcc-4.8.3 en utilisant le drapeau "-Wa, q" (car GAS ne prend pas en charge ces nouvelles instructions).
la source
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 sur Ubuntu 16) ouclang randu.c -o randu
(Clang 3.8.0) fonctionne, mais vide le noyau au moment de l'exécution avecIllegal instruction (core dumped)
. Des idées?rand()
. J'ai essayé quelques tests et posté cette question mais je ne trouve pas encore de réponse définitive.Comme dit précédemment, modulo n'est pas suffisant car il fausse la distribution. Voici mon code qui masque les bits et les utilise pour s'assurer que la distribution n'est pas biaisée.
Le code simple suivant vous permet d'examiner la distribution:
la source
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
Je comprends que modulo est une opération beaucoup plus lente que le masquage, mais je pense toujours ..... il devrait être testé.rand()
renvoie unint
dans la plage[0..RAND_MAX]
. Cette plage peut facilement être une sous-plage deuint32_t
etrandomInRange(0, ,b)
ne génère jamais de valeurs dans la plage(INT_MAX...b]
.Renvoie un nombre à virgule flottante dans la plage [0,1]:
la source