Comment générer un nombre entier aléatoire à partir d'une plage

108

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?

Jamie Keeling
la source
3
si vous regardez la deuxième réponse à la question à laquelle vous faites référence, vous avez la réponse. rand ()% 6.
Mats Fredriksson
2
Je ne comprenais pas comment cela fonctionnait, alors j'ai décidé de poser une question distincte pour plus de clarté.
Jamie Keeling
2
Pensée aléatoire: si vous interrogez un échantillon représentatif aléatoire de programmeurs, vous constaterez qu'un nombre aléatoire d'entre eux réfléchissent au hasard à des moyens de générer des nombres au hasard. Considérant que l'Univers est régi par des lois précises et prévisibles, n'est-il pas intéressant d'essayer de générer des choses de manière plus aléatoire? Des questions comme celle-ci ont toujours tendance à faire ressortir les plus de 10 000 affiches.
Armstrongest
2
@Mats rand ()% 6 peut renvoyer un 0. Pas bon pour un dé.
nouveau123456
Pouvez-vous marquer stackoverflow.com/a/6852396/419 comme réponse acceptée au lieu de la réponse qui y renvoie :) Merci.
Kev

Réponses:

173

Toutes les réponses à ce jour sont mathématiquement fausses. Le retour rand() % Nne donne pas uniformément un nombre dans la plage à [0, N)moins de Ndiviser la longueur de l'intervalle dans lequel rand()renvoie (c'est-à-dire une puissance de 2). De plus, on ne sait pas si les modules de rand()sont indépendants: il est possible qu'ils disparaissent 0, 1, 2, ..., ce qui est uniforme mais pas très aléatoire. La seule hypothèse qu'il semble raisonnable de faire est celle qui rand()é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 de rand()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, si RAND_MAX == 11et vous voulez une plage de 1..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 doublepré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:

#include <stdlib.h> // For random(), RAND_MAX

// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= RAND_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_rand = (unsigned long) RAND_MAX + 1,
    bin_size = num_rand / num_bins,
    defect   = num_rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

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 que rand()car il a une meilleure distribution (comme indiqué par la page de manuel pour rand()).

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 fonction random_extended()qui extrait les nbits (en utilisant random_at_most()) et les retourne [0, 2**n), puis appliquez random_at_most()avec random_extended()à la place de random()(et 2**n - 1à la place de RAND_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]utilisant min + random_at_most(max - min), y compris des valeurs négatives.

Ryan Reich
la source
1
@Adam Rosenfield, @ Ryan Reich: Dans une question connexe à laquelle Adam avait répondu: stackoverflow.com/questions/137783/ ... la réponse la plus votée: l'utilisation de «module» serait alors incorrecte, non? Pour générer 1..7 à partir de 1..21, la procédure décrite par Ryan doit être utilisée. Veuillez me corriger si je me trompe.
Arvind
1
Après un examen plus approfondi, un autre problème ici est que cela ne fonctionnera pas quand max - min > RAND_MAX, ce qui est plus grave que le problème que j'ai indiqué ci-dessus (par exemple, VC ++ RAND_MAXn'en a que 32767).
entre
2
La boucle while pourrait être rendue plus lisible. Plutôt que d'effectuer une affectation au conditionnel, vous voulez probablement un fichier do {} while().
theJPster
4
Hé, cette réponse est citée par le livre Comet OS;) Première fois que je vois cela dans un livre d'enseignement
vpuente
3
Il est également cité dans le livre OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Chapitre 9, Page 4)
rafascar
33

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 >= minet 1+max-min < RAND_MAX.

unsigned int rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = RAND_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = rand();
    } while (r >= limit);

    return min + (r / buckets);
}
theJPster
la source
28
Notez que cela restera bloqué dans une boucle infinie si range> = RAND_MAX. Demandez-moi comment je sais: /
theJPster
24
Comment le sais-tu!?
Fantastique Mr Fox
1
Notez que vous comparez un int à un int non signé (r> = limite). Le problème est facilement résolu en créant limitun int (et éventuellement bucketaussi) puisque RAND_MAX / range< INT_MAXet buckets * range<= RAND_MAX. EDIT: J'ai soumis et édité la proposition.
rrrrrrrrrrrrrrr
la solution de @Ryan Reich me donne toujours une meilleure distribution (moins biaisée)
Vladimir
20

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:

r = (rand() % (max + 1 - min)) + min
Sattar
la source
9
Comme indiqué dans la réponse de Ryan, cela produit un résultat biaisé.
David Wolever
6
Résultat biaisé, intdébordement potentiel avec max+1-min.
chux - Réintégrer Monica
1
cela ne fonctionne qu'avec des nombres entiers min et max. Si le min et le max sont flottants, il n'est pas possible de faire l'opération%
Taioli Francesco
17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}

Voir ici pour d'autres options.

nos
la source
2
@ S.Lott - pas vraiment. Chacun distribue différemment les cas légèrement plus élevés, c'est tout. Le double calcul donne l'impression qu'il y a plus de précision là-bas, mais vous pouvez tout aussi facilement utiliser (((max-min+1)*rand())/RAND_MAX)+minet obtenir probablement exactement la même distribution (en supposant que RAND_MAX est suffisamment petit par rapport à int pour ne pas déborder).
Steve314
4
Ceci est légèrement dangereux: il est possible que cela retourne (très rarement) max + 1, si l'un rand() == RAND_MAXou l' autre , ou rand()est très proche, RAND_MAXet des erreurs en virgule flottante repoussent le résultat final max + 1. Pour être sûr, vous devez vérifier que le résultat est dans la plage avant de le renvoyer.
Mark Dickinson
1
@Christoph: Je suis d'accord RAND_MAX + 1.0. Je ne suis toujours pas sûr que ce soit suffisant pour empêcher un max + 1retour: en particulier, le + minà la fin implique un tour qui pourrait finir par produire max + 1pour 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.
Mark Dickinson
3
Si RAND_MAXest remplacé par RAND_MAX+1.0comme Christoph suggère, je crois que cela est sans danger à condition que l' + minon 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 cela max - min + 1est exactement représentable comme un double, mais ce sera vrai sur une machine typique), il est toujours vrai que x * scaled < xpour tout double positif xet tout double scaledsatisfaisant 0.0 <= scaled && scaled < 1.0.
Mark Dickinson
1
Échec pendant randr(0, UINT_MAX): génère toujours 0.
chux - Réintègre Monica
12

Ne feriez-vous pas simplement:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

%est l'opérateur de module. Essentiellement, il divise simplement par 6 et renvoie le reste ... de 0 à 5

Armstrongest
la source
1
Il donnera des résultats de 1 à 6. C'est à cela que sert le + 1.
Armstrongest
4
Simon, montre-moi une libc en cours d'utilisation n'importe où où 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.
Joey
@Johannes: Il est donc prudent de dire que les machines à sous n'utilisent pas de module?
Armstrongest
Comment exclure un 0? Il semble que si je l'exécute dans une boucle de 30, peut-être que la deuxième ou la troisième fois, il y a un 0 à peu près à mi-chemin. Est-ce une sorte de hasard?
Jamie Keeling
@Johannes: Ce n'est peut-être pas tellement un problème de nos jours, mais traditionnellement, l'utilisation des bits de poids faible n'est pas recommandée. c-faq.com/lib/randrange.html
jamesdlin
9

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:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...

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ù iest le nombre d'itérations) et effectue une longue multiplication par n.

Lorsque le nombre de bits est suffisamment grand par rapport à n, la polarisation devient incommensurablement petite.

Peu importe si RAND_MAX + 1est 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 si RAND_MAX * nest grand.

sh1
la source
2
RAND_MAXest souvent INT_MAX, donc RAND_MAX + 1-> UB (comme INT_MIN)
chux - Réintégrer Monica
@chux c'est ce que je veux dire à propos de "il faut prendre soin d'éviter un débordement d'entier s'il RAND_MAX * nest grand". Vous devez vous organiser pour utiliser les types appropriés à vos besoins.
sh1
@chux " RAND_MAXest souvent INT_MAX" Oui, mais uniquement sur les systèmes 16 bits! Toute architecture raisonnablement moderne se situera INT_MAXà 2 ^ 32/2 et RAND_MAXà 2 ^ 16 / 2. Est-ce une hypothèse incorrecte?
chat
2
@cat Testé aujourd'hui 2 intcompilateurs 32 bits , j'ai trouvé RAND_MAX == 32767sur l'un et RAND_MAX == 2147483647sur l'autre. Mon expérience globale (des décennies) est que RAND_MAX == INT_MAXplus souvent. Donc pas d'accord qu'une architecture 32 bits raisonnablement moderne aura certainement un RAND_MAXat 2^16 / 2. Puisque la spécification C le permet 32767 <= RAND_MAX <= INT_MAX, je code de toute façon plutôt qu'une tendance.
chux
3
Toujours couvert par "il faut veiller à éviter un débordement d'entier".
sh1
4

Afin d'éviter le biais modulo (suggéré dans d'autres réponses), vous pouvez toujours utiliser:

arc4random_uniform(MAX-MIN)+MIN

Où "MAX" est la limite supérieure et "MIN" est la limite inférieure. Par exemple, pour les nombres entre 10 et 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Solution simple et meilleure que d'utiliser "rand ()% N".

magamig
la source
1
Woohoo, c'est un milliard de fois mieux que les autres réponses. Il convient de noter que vous devez d' #include <bsd/stdlib.h>abord. Aussi, une idée de comment obtenir cela sur Windows sans MinGW ou CygWin?
chat
1
Non, ce n'est pas en soi meilleur que les autres réponses, car les autres réponses sont plus génériques. Ici, vous êtes limité à arc4random, les autres réponses vous permettent de choisir une source aléatoire différente, d'opérer avec différents types de nombres, ... et enfin et surtout, elles pourraient aider quelqu'un à comprendre le problème. N'oubliez pas que la question est également intéressante pour d'autres personnes qui pourraient avoir des exigences particulières ou aucun accès à arc4random ... Néanmoins, si vous y avez accès et que vous voulez une solution rapide, c'est en effet une très bonne réponse 😊
K. Biermann
4

Voici un algorithme légèrement plus simple que la solution de Ryan Reich:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = rand();
    while (randVal >= limit) randVal = rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (RAND_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to rand() => 13
     13 is not in the bucket-range anymore (>= limit), while-condition is true
         retry...
2nd call to rand() => 7
     7 is in the bucket-range (< limit), while-condition is false
         Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
K. Biermann
la source
1
RAND_MAX + 1peut facilement déborder d' intaddition. Dans ce cas, (RAND_MAX + 1) % rangegénérera des résultats douteux. Considérer(RAND_MAX + (uint32_t)1)
chux - Réintégrer Monica le
2

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:

  • Il existe une source de caractère aléatoire, produisant des nombres entiers dans une plage [0, MAX)avec une distribution uniforme.
  • L'objectif est de produire des nombres entiers aléatoires uniformément répartis dans la plage [rmin, rmax]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), si rmin == 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 avec rand()).

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:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

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

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

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

Souris
la source
Compilé avec gcc randu.c -o randu -Wa,q(GCC 5.3.1 sur Ubuntu 16) ou clang randu.c -o randu(Clang 3.8.0) fonctionne, mais vide le noyau au moment de l'exécution avec Illegal instruction (core dumped). Des idées?
chat
Premièrement, je ne sais pas si votre processeur prend en charge réellement l'instruction RDRAND. Votre système d'exploitation est assez récent, mais le processeur ne l'est peut-être pas. Deuxièmement (mais c'est moins probable) - Je n'ai aucune idée du type d'assembleur qu'Ubuntu inclut (et Ubuntu a tendance à être assez à l'envers par rapport aux packages de mise à jour). Consultez le site Intel auquel j'ai fait référence pour savoir si votre processeur prend en charge RDRAND.
Souris le
Vous avez en effet de bons points. Ce que je ne peux toujours pas comprendre, c'est ce qui ne va pas rand(). J'ai essayé quelques tests et posté cette question mais je ne trouve pas encore de réponse définitive.
myradio
1

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.

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

Le code simple suivant vous permet d'examiner la distribution:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t rand = random_in_range(0,n - 1);
        if(rand >= n){
            printf("bug: rand out of range %u\n",(unsigned int)rand);
            return 1;
        }
        numbers[rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}
Andrew Chambers
la source
Devient assez inefficace lorsque vous rejetez les nombres du rand (). Ce sera particulièrement inefficace lorsque la plage a une taille qui peut être écrite comme 2 ^ k + 1. Alors près de la moitié de toutes vos tentatives d'un appel lent rand () seront rejetées par la condition. Serait-il préférable de calculer la gamme modulo RAND_MAX. Comme: 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é.
Øystein Schønning-Johansen
rand()renvoie un intdans la plage [0..RAND_MAX]. Cette plage peut facilement être une sous-plage de uint32_tet randomInRange(0, ,b)ne génère jamais de valeurs dans la plage (INT_MAX...b].
chux - Réintégrer Monica le
0

Renvoie un nombre à virgule flottante dans la plage [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Gérémie
la source