What is a random library in C++?

Aleatoriedad en C++: De rand() a <random>

20/05/2022

Valoración: 4.11 (5887 votos)

La generación de números aleatorios es una necesidad fundamental en una vasta gama de aplicaciones, desde videojuegos y simulaciones científicas hasta la criptografía. Sin embargo, muchos programadores de C++, especialmente aquellos que aprendieron con estándares más antiguos, siguen recurriendo a las funciones heredadas de C, rand() y srand(). Si alguna vez te has preguntado "¿por qué mi programa siempre genera la misma secuencia de números 'aleatorios'?", este artículo es para ti. Exploraremos las limitaciones de este enfoque tradicional y te guiaremos a través del poderoso y flexible sistema introducido en C++11: la biblioteca <random>.

How to generate a random number in JavaScript?
int dice_roll = distribution(generator); // generates number in the range 1..6 int wisdom = dice()+dice()+dice(); Except for random_device, all standard generators defined in the library are random number engines, which are a kind of generators that use a particular algorithm to generate series of pseudo-random numbers.
Índice de Contenido

El Legado de `rand()` y `srand()`: ¿Por Qué Debemos Evitarlos?

Durante décadas, la dupla rand() y srand() fue la forma estándar de introducir aleatoriedad en los programas. La idea es simple: llamas a srand(semilla) una vez al inicio de tu programa para "sembrar" el generador de números pseudoaleatorios. Luego, cada llamada a rand() te devuelve el siguiente número en una secuencia predeterminada por esa semilla. Si usas la misma semilla, obtendrás exactamente la misma secuencia de números, una propiedad útil para la depuración pero un problema si buscas imprevisibilidad.

El problema más común surge del método de siembra. La práctica habitual es usar el tiempo actual como semilla: srand(time(NULL)). El inconveniente es que time(NULL) a menudo tiene una resolución de solo un segundo. Si ejecutas tu programa varias veces dentro del mismo segundo, usará la misma semilla y, por lo tanto, la primera llamada a rand() producirá exactamente el mismo número. Esto es fatal para aplicaciones que requieren una aleatoriedad robusta.

Más allá de este problema de siembra, rand() tiene deficiencias más profundas:

  • Calidad Cuestionable: El estándar de C++ no ofrece ninguna garantía sobre la calidad del algoritmo de aleatoriedad utilizado por rand(). Algunas implementaciones históricas eran notoriamente malas, con patrones predecibles (como que el bit de orden inferior simplemente alternara entre 0 y 1).
  • Distribución No Uniforme: A menudo, los programadores usan el operador módulo (%) para limitar el resultado de rand() a un rango específico, por ejemplo, rand() % 6 + 1 para simular un dado. Esto introduce un sesgo sutil. Si RAND_MAX no es un múltiplo perfecto del tamaño de tu rango, algunos resultados aparecerán con más frecuencia que otros.
  • No es Seguro para Hilos (Not Thread-Safe):rand() utiliza un estado global oculto. Si múltiples hilos llaman a rand() simultáneamente, pueden producirse condiciones de carrera, corrompiendo el estado del generador y llevando a resultados impredecibles o incorrectos.
  • Falta de Flexibilidad: Con rand(), estás atado a un único generador global que produce una distribución uniforme de enteros. No hay una forma sencilla de generar números con otras distribuciones, como una distribución normal (gaussiana) o de Poisson.

Por estas razones, el consenso en la comunidad de C++ moderno es claro: el uso de srand() y rand() debe ser evitado en código nuevo y considerado obsoleto.

What is a random library in C++?
Since the ISO/IEC 14882:2011 standard was published, i.e., C++11, the random library has been a part of the C++ programming language for a while now. This library comes equipped with multiple PRNGs, and different distribution types such as: uniform distribution, normal distribution, binomial distribution, etc.

La Revolución de `<random>`: El Enfoque Moderno en C++

A partir de C++11, se introdujo la biblioteca <random>, un marco de trabajo completo y sofisticado para la generación de números aleatorios. Su filosofía central es la separación de responsabilidades, dividiendo el proceso en dos componentes clave: los motores y las distribuciones.

1. Motores de Aleatoriedad (Engines)

Un motor es un objeto que genera una secuencia de bits pseudoaleatorios sin procesar. A diferencia del generador global y oculto de rand(), con <random> tú controlas explícitamente la instancia del motor. Esto significa que puedes tener múltiples generadores independientes, guardarlos, restaurarlos y garantizar la reproducibilidad de tus secuencias de manera controlada.

La biblioteca ofrece varios tipos de motores, cada uno con diferentes compromisos en términos de velocidad, tamaño de estado y calidad de la secuencia. El más recomendado para uso general es el Mersenne Twister, implementado como std::mt19937. Es un generador de altísima calidad con un período increíblemente largo (el tiempo que tarda la secuencia en repetirse), lo que lo hace adecuado para casi todas las aplicaciones no criptográficas.

2. Distribuciones (Distributions)

Las distribuciones son el segundo pilar del sistema. Un objeto de distribución toma la secuencia de bits crudos generada por un motor y la transforma en números que siguen una distribución estadística específica. Esto es inmensamente poderoso.

What is a random number library?
The random number library provides classes that generate random and pseudo-random numbers. These classes include: Uniform random bit generators (URBGs), which include both random number engines, which are pseudo-random number generators that generate integer sequences with a uniform distribution, and true random number generators (if available).

¿Necesitas un entero en un rango específico sin el sesgo del operador módulo? Usa std::uniform_int_distribution.

¿Necesitas un número de punto flotante entre 0.0 y 1.0? Usa std::uniform_real_distribution.

¿Estás simulando fenómenos naturales que siguen una curva de campana? Usa std::normal_distribution.

Is Rand a good random number generator?
In the past, some implementations of rand () have had serious shortcomings in the randomness, distribution and period of the sequence produced (in one well-known example, the low-order bit simply alternated between 1 and 0 between calls). rand () is not recommended for serious random-number generation needs.

La biblioteca proporciona docenas de distribuciones, cubriendo una amplia gama de necesidades estadísticas y de simulación.

Generando Números Aleatorios en la Práctica: Un Guía Paso a Paso

Veamos cómo combinar estos componentes para generar números aleatorios de forma correcta y moderna.

  1. Obtener una Semilla de Calidad: Para una verdadera imprevisibilidad, necesitamos una buena semilla inicial. Aquí es donde entra en juego std::random_device. Este objeto intenta obtener un número verdaderamente aleatorio del sistema operativo, a menudo aprovechando la entropía del hardware (como movimientos del ratón, ruido de E/S, etc.). Es la forma preferida de sembrar un motor para uso en producción.
  2. Crear y Sembrar el Motor: A continuación, creamos una instancia de nuestro motor elegido (por ejemplo, std::mt19937) y lo inicializamos con el valor obtenido de std::random_device.
  3. Definir la Distribución: Creamos una instancia del objeto de distribución que se ajuste a nuestras necesidades, especificando los parámetros requeridos (como el rango mínimo y máximo para una distribución uniforme).
  4. Generar el Número: Finalmente, para obtener un número aleatorio, pasamos el objeto del motor al objeto de distribución. Este proceso se puede repetir tantas veces como sea necesario. Es importante destacar que los objetos del motor y la distribución se crean una vez y se reutilizan para generar toda la secuencia de números.

Aquí tienes un ejemplo completo que simula el lanzamiento de un dado de 6 caras:

#include <iostream> #include <random> int main() { // Paso 1: Obtener una semilla no determinista. std::random_device rd; // Paso 2: Crear y sembrar el motor Mersenne Twister. // Usamos rd() para obtener el valor de la semilla. std::mt19937 gen(rd()); // Paso 3: Definir la distribución. En este caso, un entero uniforme entre 1 y 6. std::uniform_int_distribution<> distrib(1, 6); std::cout << "Lanzando un dado 10 veces: "; for (int i = 0; i < 10; ++i) { // Paso 4: Generar el número pasando el motor a la distribución. std::cout << distrib(gen) << " "; } std::cout << std::endl; return 0; }

Tabla Comparativa: `rand()` vs. `<random>`

Para resumir las diferencias clave, aquí tienes una tabla comparativa directa:

Característicarand() / srand()Biblioteca <random>
Calidad / AleatoriedadBaja, sin garantías, dependiente de la implementación.Alta, con algoritmos estandarizados y probados como Mersenne Twister.
Flexibilidad (Distribuciones)Solo distribución uniforme de enteros. Otras distribuciones deben implementarse manualmente y con riesgo de sesgo.Amplia gama de distribuciones incorporadas (uniforme, normal, binomial, Poisson, etc.) para enteros y punto flotante.
Seguridad en Hilos (Thread Safety)No es seguro para hilos debido a su estado global.Seguro para hilos, ya que los motores son objetos. Cada hilo puede tener su propia instancia de motor.
Control y ReproducibilidadControl limitado a través de una única semilla global.Control total. Los motores son objetos que se pueden sembrar, copiar, guardar y restaurar para una reproducibilidad perfecta.
Uso RecomendadoObsoleto. Evitar en código nuevo.El estándar moderno para toda la generación de números aleatorios en C++.

Preguntas Frecuentes (FAQ)

¿Por qué mi programa con `rand()` siempre da el mismo número?
Probablemente porque lo estás ejecutando repetidamente dentro del mismo segundo y usando `time(NULL)` como semilla. La semilla no cambia, por lo que la secuencia generada es idéntica cada vez.
¿Es `rand()` suficientemente bueno para mi juego simple?
Podría funcionar, pero es una mala práctica que puede llevar a problemas difíciles de depurar. Invertir tiempo en aprender la biblioteca `<random>` es una habilidad mucho más valiosa y robusta a largo plazo. La aleatoriedad de baja calidad en los juegos puede llevar a patrones predecibles que los jugadores pueden explotar.
¿Debo crear un nuevo motor y distribución cada vez que quiero un número?
No, y esto es un error común. Debes crear una instancia del motor y de la distribución una sola vez y luego reutilizarlas en un bucle o a través de múltiples llamadas a funciones para generar la secuencia de números. Recrear y resembrar el motor en cada llamada es ineficiente y puede destruir las propiedades estadísticas del generador.
¿Cómo genero un número flotante aleatorio, por ejemplo, entre 0.0 y 1.0?
Es muy sencillo con la biblioteca moderna. Simplemente usa la distribución adecuada: std::uniform_real_distribution<double> distrib(0.0, 1.0);. Luego, llama a distrib(gen) como en el ejemplo anterior.

Conclusión

La transición de rand() a la biblioteca <random> representa un salto cualitativo en la forma en que C++ maneja la aleatoriedad. Nos aleja de una herramienta simple y defectuosa hacia un sistema modular, potente y estadísticamente sólido. Al separar los motores de las distribuciones, C++ nos da un control sin precedentes sobre cómo se generan y se forman nuestros números aleatorios. Para cualquier desarrollador serio de C++, dominar la biblioteca <random> no es solo una recomendación; es una necesidad para escribir código moderno, robusto y correcto.

Si quieres conocer otros artículos parecidos a Aleatoriedad en C++: De rand() a puedes visitar la categoría Juegos.

Subir