Números aleatorios en C para cada caso: rand y srand para juegos y simulaciones que se pueden repetir, cómo sacar un número en un rango sin sesgo, cómo mezclar un array correctamente y qué usar cuando el número es un secreto, como un token o una contraseña.
También te puede interesar
rand: para juegos y simulaciones
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
srand(2026);
for (int i = 0; i < 8; i++) {
printf("%d ", rand() % 6 + 1);
}
printf("\nRAND_MAX = %d\n", RAND_MAX);
return 0;
}salida (con glibc, siempre la misma para la semilla 2026)2 1 2 4 5 6 6 3 RAND_MAX = 2147483647
rand() devuelve un número entre 0 y RAND_MAX, y srand fija la semilla: con la misma semilla, la misma secuencia. Eso es justo lo que se quiere en una simulación o un juego que hay que poder repetir, y por eso en el ejemplo la semilla es fija. Para que cambie en cada ejecución se suele usar srand(time(nullptr)), pero tiene un problema: dos programas que arrancan en el mismo segundo sacan los mismos números.
Dos limitaciones más. La calidad de rand depende de cada biblioteca y el estándar no exige casi nada. Y rand() % 6 tiene un pequeño sesgo: si RAND_MAX + 1 no es múltiplo de 6, algunos resultados salen un poco más que otros.
Un número en un rango sin sesgo
#include <stdio.h>
#include <stdlib.h>
/* Entero uniforme entre min y max, ambos incluidos, a partir de rand(), descartando el sobrante. */
int aleatorio_entre(int min, int max)
{
unsigned rango = (unsigned) (max - min) + 1;
unsigned limite = ((unsigned) RAND_MAX + 1u) / rango * rango;
unsigned r;
do {
r = (unsigned) rand();
} while (r >= limite);
return min + (int) (r % rango);
}
int main(void)
{
srand(7);
int cuenta[6] = {};
for (int i = 0; i < 600000; i++) {
cuenta[aleatorio_entre(1, 6) - 1]++;
}
for (int i = 0; i < 6; i++) {
printf("%d: %d\n", i + 1, cuenta[i]);
}
return 0;
}salida1: 99835 2: 99946 3: 99861 4: 100240 5: 100319 6: 99799
La técnica se llama muestreo por rechazo: se descartan los valores del último tramo incompleto, los que provocarían el sesgo, y se vuelve a tirar. En 600,000 tiradas, cada cara sale unas 100,000 veces. Con un RAND_MAX tan grande como el de glibc el sesgo de % sería minúsculo, pero en sistemas donde vale 32,767 se nota.
Mezclar un array: Fisher-Yates
#include <stdio.h>
#include <stdlib.h>
static size_t entre(size_t n)
{
return (size_t) arc4random_uniform((unsigned) n);
}
void mezclar(int *v, size_t n)
{
for (size_t i = n - 1; i > 0; i--) {
size_t j = entre(i + 1);
int tmp = v[i];
v[i] = v[j];
v[j] = tmp;
}
}
int main(void)
{
int cartas[10];
for (int i = 0; i < 10; i++) cartas[i] = i + 1;
mezclar(cartas, 10);
int suma = 0;
for (int i = 0; i < 10; i++) suma += cartas[i];
printf("Mezcladas; siguen sumando %d\n", suma);
return 0;
}salidaMezcladas; siguen sumando 55
El algoritmo de Fisher-Yates recorre el array de atrás hacia delante e intercambia cada posición con una elegida al azar entre ella y el principio. Así, todas las ordenaciones posibles salen con la misma probabilidad. La versión «ingenua», intercambiar cada elemento con uno cualquiera del array entero, parece equivalente pero no lo es: favorece unas ordenaciones sobre otras. (El programa no imprime el orden porque cambia cada vez; imprime la suma, que demuestra que no se perdió ni se duplicó ninguna carta.)
Para secretos: arc4random y getrandom
#include <stdio.h>
#include <stdlib.h>
#include <sys/random.h>
int main(void)
{
unsigned char clave[16];
arc4random_buf(clave, sizeof clave);
unsigned char otra[16];
ssize_t n = getrandom(otra, sizeof otra, 0);
printf("arc4random_buf: %zu bytes, getrandom: %zd bytes\n", sizeof clave, n);
printf("PIN de un solo uso: %06u\n", arc4random_uniform(1000000));
return 0;
}salida (el PIN cambia en cada ejecución)arc4random_buf: 16 bytes, getrandom: 16 bytes PIN de un solo uso: 913859
Para tokens de sesión, contraseñas temporales, claves o códigos de verificación, rand no sirve: su estado se puede reconstruir viendo unas pocas salidas. Hay que usar el generador criptográfico del sistema. arc4random_buf y arc4random_uniform están en glibc desde la versión 2.36, en macOS y en los BSD, y no necesitan semilla; getrandom es la llamada al sistema de Linux que hay debajo. En Windows, el equivalente es BCryptGenRandom. Más en Seguridad en C.
- ParaUsa
- Simulaciones y juegos repetibles
randcon semilla fija, o un generador propio como xorshift o PCG - Un rango sin sesgomuestreo por rechazo, o
arc4random_uniform - MezclarFisher-Yates
- Secretos
arc4random_buf,getrandom; nuncarand
Todo el código de esta página se compiló con GCC 15.3 (C23, -Wall -Wextra, sin avisos) y se ejecutó en Linux, en un contenedor limpio sin acceso a la red, antes de publicarla; las salidas están copiadas de esa ejecución.