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 se N
divida 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 == 11
desea 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 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:
#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 n
bits (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 - 1
en 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.
max - min > RAND_MAX
, lo cual es más serio que el problema que mencioné anteriormente (por ejemplo, VC ++ tiene RAND_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
y 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);
}
limit
un int (y opcionalmente bucket
también) desde RAND_MAX / range
< INT_MAX
y buckets * 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:
r = (rand() % (max + 1 - min)) + min
int
desbordamiento con max+1-min
.
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.
(((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
, si rand() == RAND_MAX
o rand()
está muy cerca RAND_MAX
y 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.
RAND_MAX + 1.0
. Sin embargo, todavía no estoy seguro de que sea lo suficientemente bueno como para evitar una max + 1
devolución: en particular, + min
al final implica una ronda que podría terminar produciendo max + 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 por RAND_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 eso max - min + 1
es exactamente representable como un doble, pero eso será cierto en una máquina típica), siempre es cierto que x * scaled < x
para cualquier doble positivo x
y cualquier doble scaled
satisfactorio 0.0 <= scaled && scaled < 1.0
.
randr(0, UINT_MAX)
: siempre genera 0.
¿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
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:
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 i
es 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 + 1
es 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 * n
es grande.
RAND_MAX
es a menudo INT_MAX
, entonces RAND_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 menudo INT_MAX
" Sí, ¡pero solo en sistemas de 16 bits! Cualquier arquitectura razonablemente moderna se pondrá INT_MAX
en 2 ^ 32/2 y RAND_MAX
en 2 ^ 16 / 2. ¿Es esta una suposición incorrecta?
int
compiladores de 32 bits , encontré RAND_MAX == 32767
en uno y RAND_MAX == 2147483647
en otro. Mi experiencia general (décadas) es que RAND_MAX == INT_MAX
más a menudo. Tan en desacuerdo que una arquitectura de 32 bits razonablemente moderna sin duda tendrá un RAND_MAX
at 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.
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".
#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:
/// 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
RAND_MAX + 1
puede desbordar fácilmente la int
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]
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).
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?
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.
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]);
}
}
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 un int
en el rango [0..RAND_MAX]
. Ese rango puede ser fácilmente un subrango de uint32_t
y luego randomInRange(0, ,b)
nunca genera valores en el rango (INT_MAX...b]
.