Cómo generar un número entero aleatorio dentro de un rango

108

Esta es una continuación de una pregunta publicada anteriormente:

¿Cómo generar un número aleatorio en C?

Deseo poder generar un número aleatorio dentro de un rango particular, como 1 a 6 para imitar los lados de un dado.

¿Cómo haría esto?

Jamie Keeling
fuente
3
si observa la segunda respuesta a la pregunta a la que se refiere, tiene la respuesta. rand ()% 6.
Mats Fredriksson
2
No entendí cómo funcionaba, así que decidí hacer una pregunta separada para mayor claridad.
Jamie Keeling
2
Pensamiento aleatorio: si encuestó a una sección transversal aleatoria de programadores, encontrará que un número aleatorio de ellos está pensando aleatoriamente en formas de generar números aleatoriamente. Teniendo en cuenta que el Universo se rige por leyes precisas y predecibles, ¿no es interesante que tratemos de generar cosas de manera más aleatoria? Preguntas como esta siempre tienden a resaltar los carteles de más de 10k.
Armstrongest
2
@Mats rand ()% 6 puede devolver un 0. No es bueno para un dado.
new123456
¿Puede marcar stackoverflow.com/a/6852396/419 como la respuesta aceptada en lugar de la respuesta que la vincula :) Gracias.
Kev

Respuestas:

173

Todas las respuestas hasta ahora son matemáticamente incorrectas. La devolución rand() % Nno da de manera uniforme un número en el rango a [0, N)menos que se Ndivida la longitud del intervalo en el que se rand()devuelve (es decir, una potencia de 2). Además, uno no tiene idea de si los módulos de rand()son independientes: es posible que vayan 0, 1, 2, ..., lo cual es uniforme pero no muy aleatorio. La única suposición que parece razonable hacer es que rand()genera una distribución de Poisson: dos subintervalos cualesquiera que no se superpongan del mismo tamaño son igualmente probables e independientes. Para un conjunto finito de valores, esto implica una distribución uniforme y también asegura que los valores de rand()estén bien dispersos.

Esto significa que la única forma correcta de cambiar el rango de rand()es dividirlo en cuadros; por ejemplo, si RAND_MAX == 11desea un rango de 1..6, debe asignar {0,1}a 1, {2,3}a 2, y así sucesivamente. Estos son intervalos separados, de igual tamaño y, por lo tanto, se distribuyen de manera uniforme e independiente.

La sugerencia de utilizar la división de punto flotante es matemáticamente plausible, pero en principio adolece de problemas de redondeo. Quizás doublesea ​​lo suficientemente alta precisión para que funcione; talvez no. No lo sé y no quiero tener que resolverlo; en cualquier caso, la respuesta depende del sistema.

La forma correcta es usar aritmética de números enteros. Es decir, quieres algo como lo siguiente:

#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;
}

El bucle es necesario para obtener una distribución perfectamente uniforme. Por ejemplo, si le dan números aleatorios del 0 al 2 y solo quiere números del 0 al 1, siga tirando hasta que no obtenga un 2; no es difícil comprobar que esto da 0 o 1 con la misma probabilidad. Este método también se describe en el enlace que nos dieron en su respuesta, aunque codificado de manera diferente. Estoy usando en random()lugar de rand()porque tiene una mejor distribución (como se indica en la página de manual de rand()).

Si desea obtener valores aleatorios fuera del rango predeterminado [0, RAND_MAX], debe hacer algo complicado. Quizás lo más conveniente es definir una función random_extended()que extraiga nbits (usando random_at_most()) y regrese [0, 2**n), y luego aplique random_at_most()con random_extended()en lugar de random()(y 2**n - 1en lugar de RAND_MAX) para extraer un valor aleatorio menor que 2**n, asumiendo que tiene un tipo numérico que puede contener tal un valor. Finalmente, por supuesto, puede obtener valores al [min, max]usar min + random_at_most(max - min), incluidos los valores negativos.

Ryan Reich
fuente
1
@Adam Rosenfield, @ Ryan Reich: En una pregunta relacionada donde Adam había respondido: stackoverflow.com/questions/137783/… la respuesta más votada: el uso de 'módulo' sería incorrecto, ¿no? Para generar 1..7 a partir de 1..21, se debe utilizar el procedimiento descrito por Ryan. Por favor, corríjame si me equivoco.
Arvind
1
En una revisión adicional, otro problema aquí es que esto no funcionará cuando max - min > RAND_MAX, lo cual es más serio que el problema que mencioné anteriormente (por ejemplo, VC ++ tiene RAND_MAXsolo 32767).
Interjay
2
El bucle while podría hacerse más legible. En lugar de realizar una asignación en el condicional, probablemente desee un do {} while().
theJPster
4
Oye, esta respuesta es citada por el libro Comet OS;) La primera vez que veo eso en un libro de enseñanza
vpuente
3
También se cita en el libro OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Capítulo 9, Página 4)
rafascar
33

Siguiendo la respuesta de @Ryan Reich, pensé en ofrecer mi versión limpia. La primera verificación de límites no es necesaria dada la segunda verificación de límites, y la he hecho iterativa en lugar de recursiva. Devuelve valores en el rango [min, max], donde max >= miny 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
fuente
28
Tenga en cuenta que esto se atascará en un bucle infinito si range> = RAND_MAX. Pregúntame cómo lo sé: /
theJPster
24
¿¡Cómo lo sabes!?
Fantastic Mr Fox
1
Tenga en cuenta que está comparando un int con un int sin signo (r> = límite). El problema se resuelve fácilmente haciendo limitun int (y opcionalmente buckettambién) desde RAND_MAX / range< INT_MAXy buckets * range<= RAND_MAX. EDITAR: He enviado y editado la propuesta.
rrrrrrrrrrrrrrrr
la solución de @Ryan Reich todavía me da una mejor distribución (menos sesgada)
Vladimir
20

Aquí hay una fórmula si conoce los valores máximo y mínimo de un rango, y desea generar números incluidos entre el rango:

r = (rand() % (max + 1 - min)) + min
Sattar
fuente
9
Como se señaló en la respuesta de Ryan, esto produce un resultado sesgado.
David Wolever
6
Resultado sesgado, potencial intdesbordamiento con max+1-min.
chux - Reincorporación a Monica
1
esto funciona solo con números enteros mínimo y máximo. Si el mínimo y el máximo son flotantes, no es posible realizar la operación%
Taioli Francesco
17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

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

Vea aquí otras opciones.

nos
fuente
2
@ S.Lott - en realidad no. Cada uno distribuye los casos de probabilidades ligeramente más altas de manera diferente, eso es todo. La doble matemática da la impresión de que hay más precisión allí, pero podría usar (((max-min+1)*rand())/RAND_MAX)+miny obtener probablemente la misma distribución exacta (asumiendo que RAND_MAX es lo suficientemente pequeño en relación con int como para no desbordarse).
Steve 314
4
Esto es un poco peligroso: es posible que esto (muy raramente) regrese max + 1, si rand() == RAND_MAXo rand()está muy cerca RAND_MAXy los errores de punto flotante hacen que el resultado final pase max + 1. Para estar seguro, debe verificar que el resultado esté dentro del rango antes de devolverlo.
Mark Dickinson
1
@Christoph: Estoy de acuerdo RAND_MAX + 1.0. Sin embargo, todavía no estoy seguro de que sea lo suficientemente bueno como para evitar una max + 1devolución: en particular, + minal final implica una ronda que podría terminar produciendo max + 1valores grandes de rand (). Es más seguro abandonar este enfoque por completo y usar aritmética de números enteros.
Mark Dickinson
3
Si RAND_MAXse sustituye por RAND_MAX+1.0como sugiere Christoph, entonces yo creo que esto es seguro siempre y cuando el + minse hace usando aritmética de enteros: return (unsigned int)((max - min + 1) * scaled) + min. La razón (no obvia) es que suponiendo que la aritmética IEEE 754 y la mitad redonda a par, (y también eso max - min + 1es exactamente representable como un doble, pero eso será cierto en una máquina típica), siempre es cierto que x * scaled < xpara cualquier doble positivo xy cualquier doble scaledsatisfactorio 0.0 <= scaled && scaled < 1.0.
Mark Dickinson
1
Falla por randr(0, UINT_MAX): siempre genera 0.
chux - Reincorporar a Monica
12

¿No harías simplemente:

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

%es el operador de módulo. Básicamente, se dividirá entre 6 y devolverá el resto ... de 0 a 5

Armstrongest
fuente
1
Dará resultados del 1 al 6. Para eso es el + 1.
Armstrongest
4
Simon, muéstrame una libc en uso en cualquier lugar donde rand()incluya los bits de orden inferior del estado del generador (si usa un LCG). No he visto uno hasta ahora; todos ellos (sí, incluido MSVC con RAND_MAX siendo solo 32767) eliminan los bits de orden inferior. No se recomienda el uso de módulo por otras razones, a saber, que sesga la distribución a favor de números más pequeños.
Joey
@Johannes: ¿Entonces es seguro decir que las máquinas tragamonedas no usan módulo?
Armstrongest
¿Cómo excluiría un 0? Parece que si lo ejecuto en un bucle de 30, tal vez la segunda o tercera vez que se ejecuta hay un 0 aproximadamente a la mitad. ¿Es esto una especie de casualidad?
Jamie Keeling
@Johannes: Tal vez no sea un problema hoy en día, pero tradicionalmente no es aconsejable usar bits de bajo orden. c-faq.com/lib/randrange.html
jamesdlin
9

Para aquellos que entienden el problema del sesgo pero no pueden soportar el tiempo de ejecución impredecible de los métodos basados ​​en el rechazo, esta serie produce un número entero aleatorio progresivamente menos sesgado en el [0, n-1]intervalo:

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

Lo hace sintetizando un número aleatorio de i * log_2(RAND_MAX + 1)bits de punto fijo de alta precisión (donde ies el número de iteraciones) y realizando una multiplicación larga por n.

Cuando el número de bits es suficientemente grande en comparación con n, el sesgo se vuelve inconmensurablemente pequeño.

No importa si RAND_MAX + 1es menor que n(como en esta pregunta ), o si no es una potencia de dos, pero se debe tener cuidado para evitar el desbordamiento de enteros si RAND_MAX * nes grande.

sh1
fuente
2
RAND_MAXes a menudo INT_MAX, entonces RAND_MAX + 1-> UB (como INT_MIN)
chux - Reincorporar a Monica
@chux, a eso me refiero con "se debe tener cuidado para evitar el desbordamiento de enteros si RAND_MAX * nes grande". Debe hacer arreglos para usar tipos apropiados para sus requisitos.
sh1
@chux " RAND_MAXes a menudo INT_MAX" Sí, ¡pero solo en sistemas de 16 bits! Cualquier arquitectura razonablemente moderna se pondrá INT_MAXen 2 ^ 32/2 y RAND_MAXen 2 ^ 16 / 2. ¿Es esta una suposición incorrecta?
gato
2
@cat Probado hoy 2 intcompiladores de 32 bits , encontré RAND_MAX == 32767en uno y RAND_MAX == 2147483647en otro. Mi experiencia general (décadas) es que RAND_MAX == INT_MAXmás a menudo. Tan en desacuerdo que una arquitectura de 32 bits razonablemente moderna sin duda tendrá un RAND_MAXat 2^16 / 2. Dado que la especificación C lo permite 32767 <= RAND_MAX <= INT_MAX, codifico eso de todos modos en lugar de una tendencia.
chux - Reincorporar a Monica
3
Aún cubierto por "se debe tener cuidado para evitar el desbordamiento de enteros".
sh1
4

Para evitar el sesgo de módulo (sugerido en otras respuestas) siempre puede usar:

arc4random_uniform(MAX-MIN)+MIN

Donde "MAX" es el límite superior y "MIN" es el límite inferior. Por ejemplo, para números entre 10 y 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Solución simple y mejor que usar "rand ()% N".

magamig
fuente
1
Woohoo, esto es mil millones de veces mejor que las otras respuestas. Vale la pena señalar que #include <bsd/stdlib.h>primero debes hacerlo . Además, ¿alguna idea de cómo conseguir esto en Windows sin MinGW o CygWin?
gato
1
No, no es en sí mejor que las otras respuestas, porque las otras respuestas son más genéricas. Aquí está limitado a arc4random, las otras respuestas le permiten elegir una fuente aleatoria diferente, operar con diferentes tipos de números, ... y por último, pero no menos importante, pueden ayudar a alguien a comprender el problema. No olvide que la pregunta también es interesante para otras personas que podrían tener algunos requisitos especiales o no tener acceso a arc4random ... No obstante, si tiene acceso a ella y desea una solución rápida, de hecho es una muy buena respuesta 😊
K. Biermann
4

Aquí hay un algoritmo ligeramente más simple que la solución 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
fuente
1
RAND_MAX + 1puede desbordar fácilmente la intadición. En ese caso, (RAND_MAX + 1) % rangegenerará resultados cuestionables. Considere(RAND_MAX + (uint32_t)1)
chux - Reincorporar a Monica
2

Si bien Ryan tiene razón, la solución puede ser mucho más simple en función de lo que se conoce sobre la fuente de la aleatoriedad. Para volver a plantear el problema:

  • Existe una fuente de aleatoriedad, que genera números enteros en un rango [0, MAX)con distribución uniforme.
  • El objetivo es producir números enteros aleatorios distribuidos uniformemente en el rango [rmin, rmax]donde 0 <= rmin < rmax < MAX.

En mi experiencia, si el número de bins (o "cajas") es significativamente menor que el rango de los números originales, y la fuente original es criptográficamente fuerte, no hay necesidad de pasar por todo ese rigamarole, y una simple división de módulo sería son suficientes (como output = rnd.next() % (rmax+1), si rmin == 0), y producen números aleatorios que se distribuyen uniformemente "lo suficiente", y sin ninguna pérdida de velocidad. El factor clave es la fuente de aleatoriedad (es decir, niños, no intentes esto en casa rand()).

Aquí hay un ejemplo / prueba de cómo funciona en la práctica. Quería generar números aleatorios del 1 al 22, con una fuente criptográficamente fuerte que produjera bytes aleatorios (basado en Intel RDRAND). Los resultados son:

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%

Esto es lo más parecido al uniforme que necesito para mi propósito (lanzamiento de dados justo, generación de libros de códigos criptográficamente fuertes para máquinas de cifrado de la Segunda Guerra Mundial como http://users.telenet.be/d.rijmenants/en/kl-7sim.htm , etc. ). La salida no muestra ningún sesgo apreciable.

Aquí está la fuente del generador de números aleatorios criptográficamente fuerte (verdadero): Generador de números aleatorios digitales Intel y un código de muestra que produce números aleatorios de 64 bits (sin firmar).

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;
}

Lo compilé en Mac OS X con clang-6.0.1 (directo) y con gcc-4.8.3 usando el indicador "-Wa, q" (porque GAS no admite estas nuevas instrucciones).

Ratón
fuente
Compilado con gcc randu.c -o randu -Wa,q(GCC 5.3.1 en Ubuntu 16) o clang randu.c -o randu(Clang 3.8.0) funciona, pero descarga el núcleo en tiempo de ejecución con Illegal instruction (core dumped). ¿Algunas ideas?
gato
Primero, no sé si su CPU realmente admite la instrucción RDRAND. Su sistema operativo es bastante reciente, pero es posible que la CPU no lo sea. En segundo lugar (pero esto es menos probable): no tengo idea de qué tipo de ensamblador incluye Ubuntu (y Ubuntu tiende a estar bastante al revés con los paquetes de actualización). Consulte el sitio de Intel al que me referí para ver formas de probar si su CPU es compatible con RDRAND.
Ratón
De hecho, tienes buenos puntos. Lo que todavía no puedo entender es en qué está tan mal rand(). Probé algunas pruebas y publiqué esta pregunta, pero aún no puedo encontrar una respuesta definitiva.
myradio
1

Como se dijo antes, el módulo no es suficiente porque sesga la distribución. Aquí está mi código que enmascara los bits y los usa para garantizar que la distribución no esté sesgada.

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;
        }
    }

}

El siguiente código simple le permite ver la distribución:

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
fuente
Se vuelve bastante ineficiente cuando rechaza números del rand (). Esto será especialmente ineficaz cuando el rango tenga un tamaño que se pueda escribir como 2 ^ k + 1. Entonces, casi la mitad de todos sus intentos de una llamada rand () lenta serán rechazados por la condición. ¿Sería mejor calcular el rango de módulo RAND_MAX? Me gusta: v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;Entiendo que el módulo es una operación mucho más lenta que el enmascaramiento, pero todavía creo que ... debería probarse.
Øystein Schønning-Johansen
rand()devuelve un inten el rango [0..RAND_MAX]. Ese rango puede ser fácilmente un subrango de uint32_ty luego randomInRange(0, ,b)nunca genera valores en el rango (INT_MAX...b].
chux - Reincorporar a Monica
0

Devolverá un número de coma flotante en el rango [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Geremia
fuente