Entonces, rand()
es un generador de números pseudoaleatorio que elige un número natural entre 0 y RAND_MAX
, que es una constante definida en cstdlib
(consulte este artículo para obtener una descripción general sobre rand()
).
¿Qué sucede si quieres generar un número aleatorio entre digamos 0 y 2? En aras de la explicación, digamos que RAND_MAX
es 10 y decido generar un número aleatorio entre 0 y 2 llamando rand()%3
. ¡Sin embargo, rand()%3
no produce los números entre 0 y 2 con la misma probabilidad!
Cuando rand()
devuelve 0, 3, 6 o 9 rand()%3 == 0
,. Por lo tanto, P (0) = 4/11
Cuando rand()
devuelve 1, 4, 7 o 10 rand()%3 == 1
,. Por lo tanto, P (1) = 4/11
Cuando rand()
devuelve 2, 5 u 8 rand()%3 == 2
,. Por lo tanto, P (2) = 3/11
Esto no genera los números entre 0 y 2 con igual probabilidad. Por supuesto, para rangos pequeños, este podría no ser el mayor problema, pero para un rango más grande podría sesgar la distribución, sesgando los números más pequeños.
Entonces, ¿cuándo rand()%n
devuelve un rango de números de 0 a n-1 con igual probabilidad? Cuando RAND_MAX%n == n - 1
. En este caso, junto con nuestro supuesto anterior rand()
, devuelve un número entre 0 y RAND_MAX
con igual probabilidad, las clases de módulo de n también se distribuirían por igual.
Entonces, ¿cómo resolvemos este problema? Una forma cruda es seguir generando números aleatorios hasta que obtenga un número en el rango deseado:
int x;
do {
x = rand();
} while (x >= n);
pero eso es ineficiente para valores bajos de n
, ya que solo tiene la n/RAND_MAX
posibilidad de obtener un valor en su rango, por lo que deberá realizar RAND_MAX/n
llamadas rand()
en promedio.
Un enfoque de fórmula más eficiente sería tomar un rango grande con una longitud divisible por n
, por ejemplo RAND_MAX - RAND_MAX % n
, seguir generando números aleatorios hasta obtener uno que se encuentre en el rango, y luego tomar el módulo:
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
Para valores pequeños de n
, esto rara vez requerirá más de una llamada a rand()
.
Obras citadas y lecturas adicionales:
RAND_MAX%n == n - 1
_ es(RAND_MAX + 1) % n == 0
. Cuando leo el código, tiendo a entenderlo% something == 0
como "divisible por igual" más fácilmente que otras formas de calcularlo. Por supuesto, si su C ++ stdlib tieneRAND_MAX
el mismo valor queINT_MAX
,(RAND_MAX + 1)
seguramente no funcionaría; entonces el cálculo de Mark sigue siendo la implementación más segura.Seguir seleccionando un azar es una buena manera de eliminar el sesgo.
Actualizar
Podríamos hacer que el código sea rápido si buscamos una x en el rango divisible por
n
.El bucle anterior debe ser muy rápido, digamos 1 iteración en promedio.
fuente
rand()
puede devolver no es un múltiplo den
, entonces, haga lo que haga, inevitablemente obtendrá un "sesgo de módulo", a menos que descarte algunos de esos valores. user1413793 lo explica muy bien (aunque la solución propuesta en esa respuesta es realmente asquerosa).RAND_MAX+1 - (RAND_MAX+1) % n
trabajo funcione correctamente, pero sigo pensando que debería escribirseRAND_MAX+1 - ((RAND_MAX+1) % n)
en aras de la claridad.RAND_MAX == INT_MAX
(como lo hace en la mayoría de los sistemas) . Vea mi segundo comentario a @ user1413793 arriba.@ user1413793 es correcto sobre el problema. No voy a discutir eso más a fondo, excepto para hacer un punto: sí, para valores pequeños de
n
y valores grandes deRAND_MAX
, el sesgo de módulo puede ser muy pequeño. Pero el uso de un patrón inductor de sesgo significa que debe considerar el sesgo cada vez que calcule un número aleatorio y elija diferentes patrones para diferentes casos. Y si toma la decisión equivocada, los errores que presenta son sutiles y casi imposibles de probar. En comparación con solo usar la herramienta adecuada (comoarc4random_uniform
), eso es trabajo extra, no menos trabajo. Hacer más trabajo y obtener una solución peor es una ingeniería terrible, especialmente cuando hacerlo bien cada vez es fácil en la mayoría de las plataformas.Desafortunadamente, las implementaciones de la solución son todas incorrectas o menos eficientes de lo que deberían ser. (Cada solución tiene varios comentarios que explican los problemas, pero ninguna de las soluciones se ha solucionado para abordarlos). Es probable que esto confunda al buscador informal de respuestas, por lo que estoy proporcionando una implementación bien conocida aquí.
Una vez más, la mejor solución es usarlo
arc4random_uniform
en plataformas que lo proporcionan, o una solución a distancia similar para su plataforma (comoRandom.nextInt
en Java). Hará lo correcto sin costo para usted. Esta es casi siempre la llamada correcta para hacer.Si no lo tiene
arc4random_uniform
, puede usar el poder de código abierto para ver exactamente cómo se implementa en la parte superior de un RNG de mayor alcance (ar4random
en este caso, pero un enfoque similar también podría funcionar en la parte superior de otros RNG).Aquí está la implementación de OpenBSD :
Vale la pena señalar el último comentario de confirmación sobre este código para aquellos que necesitan implementar cosas similares:
La implementación de Java también se puede encontrar fácilmente (ver enlace anterior):
fuente
arcfour_random()
realmente utiliza el algoritmo RC4 real en su implementación, la salida definitivamente tendrá algún sesgo. Esperemos que los autores de su biblioteca hayan cambiado a utilizar un mejor CSPRNG detrás de la misma interfaz. Recuerdo que uno de los BSD ahora usa el algoritmo ChaCha20 para implementararcfour_random()
. Más sobre los sesgos de salida de RC4 que lo hacen inútil para la seguridad u otras aplicaciones críticas como el video póker: blog.cryptographyengineering.com/2013/03/…/dev/random
también ha usado RC4 en algunas plataformas en el pasado (Linux usa SHA-1 en modo contador). Desafortunadamente, las páginas de manual que encontré a través de la búsqueda indican que RC4 todavía está en uso en varias plataformas que ofrecenarc4random
(aunque el código real puede ser diferente).-upper_bound % upper_bound == 0
??-upper_bound % upper_bound
será de hecho 0 siint
es más ancho que 32 bits. Debería serlo(u_int32_t)-upper_bound % upper_bound)
(suponiendo queu_int32_t
es un BSD-ismo parauint32_t
).Definición
Modulo Bias es el sesgo inherente en el uso del módulo aritmético para reducir un conjunto de salida a un subconjunto del conjunto de entrada. En general, existe un sesgo siempre que el mapeo entre el conjunto de entrada y salida no se distribuye por igual, como en el caso de usar módulo aritmético cuando el tamaño del conjunto de salida no es un divisor del tamaño del conjunto de entrada.
Este sesgo es particularmente difícil de evitar en la informática, donde los números se representan como cadenas de bits: 0s y 1s. Encontrar fuentes verdaderamente aleatorias de aleatoriedad también es extremadamente difícil, pero está más allá del alcance de esta discusión. Para el resto de esta respuesta, suponga que existe una fuente ilimitada de bits verdaderamente aleatorios.
Ejemplo de problema
Consideremos simular una tirada de dado (0 a 5) usando estos bits aleatorios. Hay 6 posibilidades, por lo que necesitamos suficientes bits para representar el número 6, que es de 3 bits. Desafortunadamente, 3 bits aleatorios producen 8 resultados posibles:
Podemos reducir el tamaño del conjunto de resultados exactamente a 6 tomando el valor módulo 6, sin embargo, esto presenta el problema de sesgo de módulo :
110
produce un 0 y111
produce un 1. Este dado está cargado.Soluciones potenciales
Enfoque 0:
En lugar de confiar en bits aleatorios, en teoría uno podría contratar a un pequeño ejército para lanzar dados todo el día y registrar los resultados en una base de datos, y luego usar cada resultado solo una vez. Esto es tan práctico como parece, y lo más probable es que no produzca resultados verdaderamente aleatorios de todos modos (juego de palabras).
Enfoque 1:
En lugar de utilizar el módulo, una solución ingenua pero matemáticamente correcto es a los resultados de descarte que el rendimiento
110
y111
y simplemente tratar de nuevo con 3 nuevos bits. Desafortunadamente, esto significa que hay un 25% de posibilidades en cada lanzamiento de que se requerirá un nuevo lanzamiento, incluyendo cada uno de los mismos. Esto es claramente poco práctico para todos, excepto para los usos más triviales.Enfoque 2:
Use más bits: en lugar de 3 bits, use 4. Esto produce 16 resultados posibles. Por supuesto, volver a tirar cada vez que el resultado sea mayor que 5 empeora las cosas (10/16 = 62.5%) para que eso no ayude.
Observe que 2 * 6 = 12 <16, por lo que podemos tomar con seguridad cualquier resultado inferior a 12 y reducir ese módulo 6 para distribuir los resultados de manera uniforme. Los otros 4 resultados deben descartarse y luego volverse a tirar como en el enfoque anterior.
Suena bien al principio, pero revisemos las matemáticas:
Ese resultado es desafortunado, pero intentemos nuevamente con 5 bits:
Una mejora definitiva, pero no lo suficientemente buena en muchos casos prácticos. La buena noticia es que agregar más bits nunca aumentará las posibilidades de tener que descartar y volver a tirar . Esto es válido no solo para los dados, sino en todos los casos.
Sin embargo, como se demostró , agregar 1 bit extra puede no cambiar nada. De hecho, si aumentamos nuestro rollo a 6 bits, la probabilidad sigue siendo 6.25%.
Esto plantea 2 preguntas adicionales:
Solución general
Afortunadamente, la respuesta a la primera pregunta es sí. El problema con 6 es que 2 ^ x mod 6 cambia entre 2 y 4, que casualmente son múltiplos de 2 entre sí, de modo que para un par x> 1,
Por lo tanto, 6 es una excepción más que la regla. Es posible encontrar módulos más grandes que produzcan potencias consecutivas de 2 de la misma manera, pero eventualmente esto debe ajustarse, y la probabilidad de un descarte se reducirá.
Prueba de concepto
Aquí hay un programa de ejemplo que usa libcrypo de OpenSSL para suministrar bytes aleatorios. Al compilar, asegúrese de vincular a la biblioteca con la
-lcrypto
que casi todos deberían estar disponibles.Animo a jugar con los valores
MODULUS
yROLLS
para ver cuántas repeticiones ocurren en la mayoría de las condiciones. Una persona escéptica también puede desear guardar los valores calculados en el archivo y verificar que la distribución parezca normal.fuente
randomPool = RAND_bytes(...)
línea siempre resultarárandomPool == 1
debido a la aserción. Esto siempre da como resultado un descarte y una repetición. Creo que querías declarar en una línea separada. En consecuencia, esto hizo que el RNG volviera con1
cada iteración.randomPool
siempre se evaluará de1
acuerdo con la documentación deRAND_bytes()
OpenSSL ya que siempre tendrá éxito gracias a laRAND_status()
afirmación.Hay dos quejas habituales con el uso del módulo.
uno es válido para todos los generadores. Es más fácil de ver en un caso límite. Si su generador tiene un RAND_MAX que es 2 (que no cumple con el estándar C) y desea solo 0 o 1 como valor, el uso del módulo generará 0 el doble de veces (cuando el generador genera 0 y 2) como lo hará generar 1 (cuando el generador genera 1). Tenga en cuenta que esto es cierto tan pronto como no elimine los valores, cualquiera que sea la asignación que esté utilizando desde los valores del generador hasta el deseado, uno ocurrirá el doble de veces que el otro.
algún tipo de generador tiene sus bits menos significativos menos aleatorios que el otro, al menos para algunos de sus parámetros, pero lamentablemente esos parámetros tienen otras características interesantes (como tener RAND_MAX uno menos que una potencia de 2). El problema es bien conocido y, durante mucho tiempo, la implementación de la biblioteca probablemente lo evitará (por ejemplo, la implementación de rand () de muestra en el estándar C usa este tipo de generador, pero descarta los 16 bits menos significativos), pero a algunos les gusta quejarse eso y puede que tengas mala suerte
Usando algo como
generar un número aleatorio entre 0 yn evitará ambos problemas (y evita el desbordamiento con RAND_MAX == INT_MAX)
Por cierto, C ++ 11 introdujo formas estándar para la reducción y otro generador que no sea rand ().
fuente
La solución de Mark (la solución aceptada) es casi perfecta.
Sin embargo, tiene una advertencia que descarta 1 conjunto válido de resultados en cualquier escenario donde
RAND_MAX
(RM
) es 1 menor que un múltiplo deN
(DondeN
= el Número de posibles resultados válidos).es decir, cuando el 'conteo de valores descartados' (
D
) es igual aN
, entonces en realidad son un conjunto válido (V)
no un conjunto no válido (I
).Lo que causa esto es que en algún momento Mark pierde de vista la diferencia entre
N
yRand_Max
.N
es un conjunto cuyos miembros válidos están compuestos solo por números enteros positivos, ya que contiene un recuento de respuestas que serían válidas. (por ejemplo: SetN
={1, 2, 3, ... n }
)Rand_max
Sin embargo, es un conjunto que (como se define para nuestros propósitos) incluye cualquier número de enteros no negativos.En su forma más genérica, lo que se define aquí
Rand Max
es el Conjunto de todos los resultados válidos, que teóricamente podría incluir números negativos o valores no numéricos.Por
Rand_Max
lo tanto, se define mejor como el conjunto de "Posibles respuestas".Sin embargo,
N
opera contra el recuento de los valores dentro del conjunto de respuestas válidas, por lo que incluso según lo definido en nuestro caso específico,Rand_Max
será un valor uno menos que el número total que contiene.Usando la solución de Mark, los valores se descartan cuando: X => RM - RM% N
Como puede ver en el ejemplo anterior, cuando el valor de X (el número aleatorio que obtenemos de la función inicial) es 252, 253, 254 o 255, lo descartaríamos aunque estos cuatro valores comprendan un conjunto válido de valores devueltos .
IE: Cuando el recuento de los valores descartados (I) = N (el número de resultados válidos), la función original descartará un conjunto válido de valores de retorno.
Si describimos la diferencia entre los valores N y RM como D, es decir:
Luego, a medida que el valor de D se vuelve más pequeño, el porcentaje de repeticiones innecesarias debido a este método aumenta en cada multiplicativo natural. (Cuando RAND_MAX NO es igual a un número primo, esto es una preocupación válida)
P.EJ:
Dado que el porcentaje de Rerolls necesario aumenta cuanto más se acerca N a RM, esto puede ser una preocupación válida en muchos valores diferentes dependiendo de las restricciones del sistema que ejecuta el código y los valores que se buscan.
Para negar esto, podemos hacer una enmienda simple Como se muestra aquí:
Esto proporciona una versión más general de la fórmula que explica las peculiaridades adicionales del uso del módulo para definir sus valores máximos.
Ejemplos de uso de un valor pequeño para RAND_MAX que es un multiplicativo de N.
Versión original de Mark:
Versión generalizada 1:
Además, en el caso donde N debería ser el número de valores en RAND_MAX; en este caso, puede establecer N = RAND_MAX +1, a menos que RAND_MAX = INT_MAX.
En cuanto al bucle, podría usar N = 1, y cualquier valor de X será aceptado, sin embargo, y colocará una declaración IF para su multiplicador final. Pero quizás tenga un código que pueda tener una razón válida para devolver un 1 cuando la función se llama con n = 1 ...
Por lo tanto, puede ser mejor usar 0, que normalmente proporcionaría un error Div 0, cuando desee tener n = RAND_MAX + 1
Versión generalizada 2:
Ambas soluciones resuelven el problema con resultados válidos descartados innecesariamente que ocurrirán cuando RM + 1 sea un producto de n.
La segunda versión también cubre el escenario de caso límite cuando necesita n para igualar el conjunto total posible de valores contenidos en RAND_MAX.
El enfoque modificado en ambos es el mismo y permite una solución más general a la necesidad de proporcionar números aleatorios válidos y minimizar los valores descartados.
Reiterar:
La solución general básica que amplía el ejemplo de mark:
La solución general extendida que permite un escenario adicional de RAND_MAX + 1 = n:
En algunos idiomas (particularmente los idiomas interpretados) hacer los cálculos de la operación de comparación fuera de la condición while puede conducir a resultados más rápidos, ya que este es un cálculo único, sin importar cuántas repeticiones se requieran. YMMV!
fuente
RAND_MAX%n = n - 1
Con un
RAND_MAX
valor de3
(en realidad debería ser mucho más alto que eso, pero el sesgo aún existiría) tiene sentido a partir de estos cálculos que existe un sesgo:1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
En este caso, esto
% 2
es lo que no debe hacer cuando desea un número aleatorio entre0
y1
. Sin embargo, podría obtener un número aleatorio entre0
y2
haciendo% 3
, porque en este caso:RAND_MAX
es un múltiplo de3
.Otro método
Hay mucho más simple pero para agregar a otras respuestas, aquí está mi solución para obtener un número aleatorio entre
0
yn - 1
, por lo tanto,n
diferentes posibilidades, sin sesgos.>= n
, reinicie (sin módulo).Los datos realmente aleatorios no son fáciles de obtener, entonces, ¿por qué usar más bits de los necesarios?
A continuación se muestra un ejemplo en Smalltalk, que utiliza un caché de bits de un generador de números pseudoaleatorio. No soy un experto en seguridad, así que úselo bajo su propio riesgo.
fuente
Como indica la respuesta aceptada , "sesgo de módulo" tiene sus raíces en el bajo valor de
RAND_MAX
. Utiliza un valor extremadamente pequeño deRAND_MAX
(10) para mostrar que si RAND_MAX fuera 10, entonces trataste de generar un número entre 0 y 2 usando%, se obtendrían los siguientes resultados:Por lo tanto, hay 4 salidas de 0 (probabilidad de 4/10) y solo 3 salidas de 1 y 2 (3/10 posibilidades cada una).
Entonces es parcial. Los números más bajos tienen una mejor oportunidad de salir.
Pero eso solo aparece tan obviamente cuando
RAND_MAX
es pequeño . O más específicamente, cuando el número por el que está modificando es grande en comparación conRAND_MAX
.Una solución mucho mejor que el bucle (que es increíblemente ineficiente y ni siquiera debería sugerirse) es usar un PRNG con un rango de salida mucho mayor. El algoritmo Mersenne Twister tiene una salida máxima de 4,294,967,295. Como tal,
MersenneTwister::genrand_int32() % 10
para todos los efectos, se distribuirá por igual y el efecto de sesgo de módulo casi desaparecerá.fuente
MT::genrand_int32()%2
elige el 0 (50 + 2.3e-8)% del tiempo y el 1 (50 - 2.3e-8)% del tiempo. A menos que esté construyendo el RGN de un casino (para el que probablemente usaría un RGN de rango mucho mayor), cualquier usuario no notará un 2.3e-8% adicional del tiempo. Estás hablando de números demasiado pequeños para importar aquí.RAND_MAX
valor alto disminuirá el sesgo del módulo, pero no lo eliminará. Looping will.RAND_MAX
es lo suficientemente grande como el número por el que está modificando, el número de veces que necesita regenerar el número aleatorio es muy pequeño y no afectará la eficiencia. Le digo que siga el ciclo, siempre que esté probando contra el múltiplo más grande enn
lugar de solon
como lo propone la respuesta aceptada.Acabo de escribir un código para el método imparcial de volteo de monedas de Von Neumann, que teóricamente debería eliminar cualquier sesgo en el proceso de generación de números aleatorios. Se puede encontrar más información en ( http://en.wikipedia.org/wiki/Fair_coin )
fuente
rand() % 100
100 veces. B) si todos los resultados son diferentes, tome el primero. C) de lo contrario, GOTO A. Esto funcionará, pero con un número esperado de iteraciones de aproximadamente 10 ^ 42, tendrá que ser bastante paciente. E inmortal.else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}