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?
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?
Respuestas:
Todas las respuestas hasta ahora son matemáticamente incorrectas. La devolución
rand() % N
no da de manera uniforme un número en el rango a[0, N)
menos que seN
divida la longitud del intervalo en el que serand()
devuelve (es decir, una potencia de 2). Además, uno no tiene idea de si los módulos derand()
son independientes: es posible que vayan0, 1, 2, ...
, lo cual es uniforme pero no muy aleatorio. La única suposición que parece razonable hacer es querand()
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 derand()
estén bien dispersos.Esto significa que la única forma correcta de cambiar el rango de
rand()
es dividirlo en cuadros; por ejemplo, siRAND_MAX == 11
desea un rango de1..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
double
sea 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:
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 derand()
porque tiene una mejor distribución (como se indica en la página de manual derand()
).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ónrandom_extended()
que extraigan
bits (usandorandom_at_most()
) y regrese[0, 2**n)
, y luego apliquerandom_at_most()
conrandom_extended()
en lugar derandom()
(y2**n - 1
en lugar deRAND_MAX
) para extraer un valor aleatorio menor que2**n
, asumiendo que tiene un tipo numérico que puede contener tal un valor. Finalmente, por supuesto, puede obtener valores al[min, max]
usarmin + random_at_most(max - min)
, incluidos los valores negativos.fuente
max - min > RAND_MAX
, lo cual es más serio que el problema que mencioné anteriormente (por ejemplo, VC ++ tieneRAND_MAX
solo 32767).do {} while()
.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 >= min
y1+max-min < RAND_MAX
.fuente
limit
un int (y opcionalmentebucket
también) desdeRAND_MAX / range
<INT_MAX
ybuckets * range
<=RAND_MAX
. EDITAR: He enviado y editado la propuesta.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:
fuente
int
desbordamiento conmax+1-min
.Vea aquí otras opciones.
fuente
(((max-min+1)*rand())/RAND_MAX)+min
y 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).max + 1
, sirand() == RAND_MAX
orand()
está muy cercaRAND_MAX
y los errores de punto flotante hacen que el resultado final pasemax + 1
. Para estar seguro, debe verificar que el resultado esté dentro del rango antes de devolverlo.RAND_MAX + 1.0
. Sin embargo, todavía no estoy seguro de que sea lo suficientemente bueno como para evitar unamax + 1
devolución: en particular,+ min
al final implica una ronda que podría terminar produciendomax + 1
valores grandes de rand (). Es más seguro abandonar este enfoque por completo y usar aritmética de números enteros.RAND_MAX
se sustituye porRAND_MAX+1.0
como sugiere Christoph, entonces yo creo que esto es seguro siempre y cuando el+ min
se 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 esomax - min + 1
es exactamente representable como un doble, pero eso será cierto en una máquina típica), siempre es cierto quex * scaled < x
para cualquier doble positivox
y cualquier doblescaled
satisfactorio0.0 <= scaled && scaled < 1.0
.randr(0, UINT_MAX)
: siempre genera 0.¿No harías simplemente:
%
es el operador de módulo. Básicamente, se dividirá entre 6 y devolverá el resto ... de 0 a 5fuente
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.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:Lo hace sintetizando un número aleatorio de
i * log_2(RAND_MAX + 1)
bits de punto fijo de alta precisión (dondei
es el número de iteraciones) y realizando una multiplicación larga porn
.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 + 1
es menor quen
(como en esta pregunta ), o si no es una potencia de dos, pero se debe tener cuidado para evitar el desbordamiento de enteros siRAND_MAX * n
es grande.fuente
RAND_MAX
es a menudoINT_MAX
, entoncesRAND_MAX + 1
-> UB (como INT_MIN)RAND_MAX * n
es grande". Debe hacer arreglos para usar tipos apropiados para sus requisitos.RAND_MAX
es a menudoINT_MAX
" Sí, ¡pero solo en sistemas de 16 bits! Cualquier arquitectura razonablemente moderna se pondráINT_MAX
en 2 ^ 32/2 yRAND_MAX
en 2 ^ 16 / 2. ¿Es esta una suposición incorrecta?int
compiladores de 32 bits , encontréRAND_MAX == 32767
en uno yRAND_MAX == 2147483647
en otro. Mi experiencia general (décadas) es queRAND_MAX == INT_MAX
más a menudo. Tan en desacuerdo que una arquitectura de 32 bits razonablemente moderna sin duda tendrá unRAND_MAX
at2^16 / 2
. Dado que la especificación C lo permite32767 <= RAND_MAX <= INT_MAX
, codifico eso de todos modos en lugar de una tendencia.Para evitar el sesgo de módulo (sugerido en otras respuestas) siempre puede usar:
Donde "MAX" es el límite superior y "MIN" es el límite inferior. Por ejemplo, para números entre 10 y 20:
Solución simple y mejor que usar "rand ()% N".
fuente
#include <bsd/stdlib.h>
primero debes hacerlo . Además, ¿alguna idea de cómo conseguir esto en Windows sin MinGW o CygWin?Aquí hay un algoritmo ligeramente más simple que la solución de Ryan Reich:
fuente
RAND_MAX + 1
puede desbordar fácilmente laint
adición. En ese caso,(RAND_MAX + 1) % range
generará resultados cuestionables. Considere(RAND_MAX + (uint32_t)1)
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:
[0, MAX)
con distribución uniforme.[rmin, rmax]
donde0 <= 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)
, sirmin == 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 casarand()
).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:
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).
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).
fuente
gcc randu.c -o randu -Wa,q
(GCC 5.3.1 en Ubuntu 16) oclang randu.c -o randu
(Clang 3.8.0) funciona, pero descarga el núcleo en tiempo de ejecución conIllegal instruction (core dumped)
. ¿Algunas ideas?rand()
. Probé algunas pruebas y publiqué esta pregunta, pero aún no puedo encontrar una respuesta definitiva.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.
El siguiente código simple le permite ver la distribución:
fuente
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.rand()
devuelve unint
en el rango[0..RAND_MAX]
. Ese rango puede ser fácilmente un subrango deuint32_t
y luegorandomInRange(0, ,b)
nunca genera valores en el rango(INT_MAX...b]
.Devolverá un número de coma flotante en el rango [0,1]:
fuente