Menu

Números aleatorios en C: rand, srand y generar un número en un rango

Cómo generar números aleatorios en C con rand() y RAND_MAX, por qué se siembra con srand(time(NULL)) exactamente una vez, mapear a un rango con % y el pequeño sesgo que introduce, decimales aleatorios y secuencias reproducibles.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Los números aleatorios de C vienen de dos funciones de <stdlib.h>: rand(), que produce el siguiente valor, y srand(), que fija el punto de partida. No son realmente aleatorios: son una secuencia pseudoaleatoria, calculada de forma determinista a partir de una semilla, lo cual es una limitación para la criptografía y una virtud para las pruebas.

rand() y RAND_MAX

rand() devuelve un int en algún punto entre 0 y RAND_MAX, ambos incluidos. RAND_MAX es una macro con garantía de ser al menos 32767; en Linux y macOS es 2147483647.

Ejecútalo dos veces. Los números son idénticos ambas veces, y eso no es un error.

Sembrar con srand

Sin ninguna llamada a srand, la secuencia se comporta como si hubieras llamado a srand(1). Misma semilla, misma secuencia, en cada ejecución. Para obtener números distintos en cada ejecución, siembra con algo que cambie; por convención, la hora actual:

time(NULL) de <time.h> devuelve los segundos transcurridos desde el inicio de 1970, así que cada ejecución recibe una semilla distinta. La conversión a unsigned int calla un aviso sobre el estrechamiento de time_t.

Tres reglas sobre la siembra, y la gente se equivoca en todas:

Siembra exactamente una vez, al inicio de main. Llamar a srand antes de cada rand() es el antipatrón clásico: dentro de un bucle que termina en menos de un segundo, time(NULL) devuelve el mismo valor en cada iteración, así que vuelves a sembrar con el mismo número y rand() devuelve el mismo primer valor cada vez. La salida es una columna de números "aleatorios" idénticos.

No vuelvas a sembrar para "mejorar" la aleatoriedad. La calidad del generador viene de que su estado interno avance; reiniciar ese estado tira la secuencia a la basura.

time(NULL) tiene una resolución de un segundo. Dos programas lanzados en el mismo segundo obtienen la misma secuencia. Eso está bien para un juego y está mal para cualquier cosa donde importe la independencia.

Un número dentro de un rango

El modismo estándar usa el operador de resto:

rand() % n            /* de 0 a n-1 */
rand() % n + min      /* de min a min+n-1 */

Para obtener de min a max inclusive, la cantidad de valores posibles es max - min + 1:

El + 1 es donde viven los errores de desplazamiento por uno. rand() % 6 da de 0 a 5, así que una tirada de dado es rand() % 6 + 1. Escribir rand() % 7 + 1 para "incluir el 6" te da un dado de siete caras.

La nota honesta sobre el sesgo del módulo

rand() % n no es perfectamente uniforme salvo que n divida exactamente a RAND_MAX + 1.

Piénsalo con números pequeños. Si RAND_MAX fuera 9 —así que rand() devuelve de 0 a 9, diez valores igual de probables— entonces rand() % 3 mapea 0,3,6,9 al 0; 1,4,7 al 1; y 2,5,8 al 2. El resultado 0 ocurre de cuatro formas de diez, y los resultados 1 y 2 de tres formas cada uno. El cero es un 33% más probable.

La misma desviación existe con el RAND_MAX real, solo que mucho menor: los valores sobrantes son los primeros (RAND_MAX + 1) % n resultados, cada uno ganando una oportunidad extra de entre unos 2.100 millones. Para una tirada de dado, un mazo barajado o una simulación, eso es inmedible: usa % y sigue adelante.

Cuando sí importa —trabajo estadístico, cualquier cosa relacionada con la seguridad— rechaza los valores sobrantes en lugar de doblarlos hacia dentro:

El bucle tira el pequeño rango de valores que causaría la desviación y vuelve a sacar. Termina rápido: el trozo rechazado es una fracción insignificante del total.

Para aleatoriedad genuinamente sensible a la seguridad, rand() es la herramienta equivocada por mucho cuidado que pongas: usa arc4random_buf en macOS y BSD, getrandom() en Linux o BCryptGenRandom en Windows.

Decimales aleatorios

Divide entre RAND_MAX para aterrizar en [0.0, 1.0] y luego escala:

La conversión en (double) rand() es esencial. Sin ella, rand() / RAND_MAX es división entera y da 0 casi siempre, y 1 con la probabilidad de uno entre dos mil millones de dar con el máximo: un error que se ve como "todos mis decimales aleatorios son cero". Mira conversión de tipos para saber por qué.

Secuencias reproducibles

Una semilla fija da la secuencia idéntica en cada ejecución, que es exactamente lo que quieres para una prueba, una sesión de depuración o un juego con códigos de nivel compartibles:

La semilla 42 produce los mismos cinco números cada vez que se usa, en esta ejecución y en cualquier otra con la misma biblioteca. Esa reproducibilidad es por lo que una simulación debería permitir elegir la semilla: ejecuta con el reloj normalmente y pasa una semilla fija cuando reproduzcas un error.

Una salvedad: la secuencia para una semilla dada no es portable. Distintas bibliotecas de C usan generadores distintos, así que la semilla 42 en glibc y la semilla 42 en Windows dan números distintos. Reproducible en una máquina, no entre máquinas.

Un juego de dados

Todo junto: sembrar una vez, un auxiliar para el rango y un array que lleva la cuenta de los resultados.

El histograma debería tener su pico en el 7 y decaer hacia el 2 y el 12: hay seis formas de sumar 7 y una sola de sumar 2 o 12. Un generador que produjera aquí una distribución plana estaría roto.

Dos páginas relacionadas: la biblioteca estándar mapea el resto de <stdlib.h>, y funciones matemáticas cubre <math.h>, que querrás en cuanto los valores aleatorios alimenten cálculos de verdad.

Preguntas frecuentes

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

Incluye <stdlib.h>, siembra una vez al inicio de main con srand((unsigned) time(NULL)) (que necesita <time.h>) y luego llama a rand() para cada valor. rand() devuelve un int entre 0 y RAND_MAX, ambos incluidos.

¿Cómo obtengo un número aleatorio entre dos valores en C?

Usa rand() % (max - min + 1) + min. Para una tirada de dado entre 1 y 6 eso es rand() % 6 + 1. El % n mapea el resultado a 0..n-1 y sumar min desplaza la ventana; solo asegúrate de que el conteo incluya ambos extremos, que es lo que hace el + 1.

¿Por qué mi programa de C imprime los mismos números aleatorios cada vez?

Porque nunca llamaste a srand. Sin semilla, rand() se comporta como si estuviera sembrado con 1, así que cada ejecución produce la secuencia idéntica. Llama a srand((unsigned) time(NULL)) una vez al inicio del programa; una vez, no antes de cada llamada a rand(), lo que empeoraría las cosas.

¿Qué es el sesgo del módulo en la generación de números aleatorios?

rand() % n solo es perfectamente uniforme cuando n divide exactamente a RAND_MAX + 1. Si no, los primeros valores aparecen una vez de más a lo largo del rango completo, haciéndolos apenas más probables. Con RAND_MAX en 2147483647 y una n pequeña, la desviación está muy por debajo de lo que nota un juego o una simulación, pero para criptografía o estadística usa un bucle de rechazo o un generador adecuado.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR