Números aleatorios en C++
Una característica importante en los lenguajes de programación es la posibilidad de generar números aleatorios.
Los números aleatorios son pieza clave en la programación de juegos de azar, simulaciones, cifrado de datos, en programación de comportamiento pseudo inteligente, en modelos estadísticos, etc. Por esta razón es importante conocer las herramientas que un lenguaje de programación te ofrece en este sentido.
La función rand()
C++ define la función rand() para generar números aleatorios, está definida en la librería cstdlib.
rand() devuelve un número entero pseudo-aleatorio en el rango 0 y RAND_MAX. RAND_MAX está definida también en la librería cstdlib.
Sintaxis
int rand (void);
Como puedes observar, para usar rand() solo se invoca la función y se asigna a cualquier variable entera, no requiere parámetros.
De forma predeterminada rand() genera una valor pseudo aleatorio comprendido entre 0 y el valor de RAND_MAX, RAND_MAX está definido también el cstdlib.
Si queremos conocer el valor de RAND_MAX en nuestro sistema, simplemente hacemos una llamada a dicha constante.
cout << "El valor de RAND_MAX es este sistema es: " << RAND_MAX;
Uso básico de rand()
A continuación se muestra el uso de rand(), además se muestra como obtener el valor de RAND_MAX para tu sistema.
// cpp_72_pseudoaleatorio.cpp
// Se ilustra el uso de la función rand() para generar números pseudoaleatorios
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Revisa todo el código del tutorial en: https://github.com/carlosgbr/
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int a;
a = rand(); // Genera un valor entre 0 y RAND_MAX
cout << a << endl;
cout << "RAND_MAX para este equipo tiene un valor de: " << RAND_MAX;
return 0;
}
Acotando los valores para rand()
Podemos limitar el rango de números aleatorios que entrega rand(), podemos definir el valor inicial (el predeterminado es 0) y el límite superior.
Definiendo un límite superior para rand()
Para definir un límite superior, se utiliza la notación de módulo (%), a continuación se indica el valor máximo que se desea. por ejemplo.
valor = rand() % 2; // Obtiene valores entre 0 y 1
Por ejemplo el código anterior permite simular el lanzamiento de una moneda, y en general cualquier cosa que requiera un si o un no.
valor = rand() % 10; // Obtiene un número entre 0 y 9, nota que no incluye el 10valorrnd = 10 + rand() % 20;
valor = rand() % 1500; // Obtiene un número entre 0 y 1499
valor = rand() % 65536 ; // Obtiene un número entre 0 y 65535, por ejemplo, para elegir un puerto TCP/IP al azar
Definiendo un límite inferior para rand()
Para definir el límite inicial utilizamos la notación de suma, indicando el número desde el cual se deben generar los números, por ejemplo:
valor = 10 + rand(); // Genera números aleatorios a partir del 10
valor = 65 + rand(); // Genera números aleatorios a partir del 65
Consideraciones al usar límites inferiores y superiores con rand()
Al definir un valor inicial ocurre un comportamiento que no se ve a primera vista al usar un rango abierto, que sí es visible en rangos bien definidos, por ejemplo considera la siguiente expresión:
valor = 10 + rand() % 20;
En este caso el rango inicia en 10, pero no termina en 20, termina en 30, de modo que el rango de números generados comprenderá del 10 al 29
Si queremos que el rango de números generados comprenda del 1 al 10 la expresión debe ser:
valor = 1 + rand() % 10;
Puedes observar ahora que cuando definimos un valor inicial y un valor final, el valor final “se recorre” la cantidad del valor inicial, es decir, el valor final real es la suma del valor inicial más el valor final.
Vemos unos ejemplos adicionales,
valor = 1 + rand() % 100; // Genera un valor entre 1 y 100
valor = 25 + rand() % 100; // Genera un valor entre 25 y 124
valor = rand() %51 ; // Genera un valor entre 0 y 50
Si requerimos, por ejemplo definir números aleatorios en el rango 10 al 25, lo debemos expresar de la siguiente forma:
valor = 10 + rand() % (26-10) //Restamos la cantidad que se "recorre" el rango
de modo que podemos expresar todos los casos con una fórmula general que se aplica a cualquier caso:
variable = limite_inferior + rand() % (limite_superior +1 - limite_inferior);
en donde,
variable, variable entera para almacenar el número generado
limite_inferior, valor inicial del rango de valores generados, el número generado puede incluir éste número
rand(), función para generar números pseudo aleatorios, requiere la librería cstdlib
(limite_superior + 1 – limite_inferior), define el valor superior del rango que se utilizará para obtener números aleatorios.
limite_superior + 1, límite superior del rango deseado de números aleatorios, el “+ 1” es necesario debido a que por definición el límite superior es igual a limite_superior – 1, de esta forma se “neutralizan” quedando el límite superior como lo queremos. Haz pruebas cambiando código para que te quede claro este punto.
Uso de límites en el rango en valores pseudo aleatorios.
El siguiente programa reúne todos los casos descritos en la definición de límites para los números aleatorios obtenidos con rand(), como siempre la sugerencia es que lo modifiques hasta que no tengas dudas.
// cpp_73_pseudoaleatorioLimites.cpp
// Se ilustra el uso de la función rand() para generar números pseudoaleatorios
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Revisa todo el código del tutoral en: https://github.com/carlosgbr/
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int valor = 0;
// rand() definiendo límite superior, el rango va de 0 a (Límite - 1)
cout << "rand() definiendo límite superior, el rango va de 0 a (Límite - 1)" << endl;
valor = rand() % 2; // Obtiene valores entre 0 y 1
cout << "rand() % 2: \t\t" << valor << endl;
valor = rand() % 10; // Obtiene un número entre 0 y 9
cout << "rand() % 10: \t\t" << valor << endl;
valor = rand() % 1500; // Obtiene un número entre 0 y 1499
cout << "rand() % 1500: \t\t" << valor << endl;
valor = rand() % 65536 ; // Obtiene un número entre 0 y 65535
cout << "rand() % 65536: \t" << valor << endl << endl;
// rand() definiendo límite inferior, el rango va de
// límite inferior a RAND_MAX* (*no del todo cierto, pero nos es útil así)
cout << "rand() definiendo límite inferior" << endl;
valor = 10 + rand(); // Obtiene un número a partir de 10 hasta RAND_MAX*
cout << "10 + rand(): \t\t" << valor << endl;
valor = 65000 + rand(); // Obtiene un número a partir de 65000 hasta RAND_MAX*
cout << "65000 + rand(): \t" << valor << endl << endl;
// rand() definiendo límite superior, e inferior
// El compilador calcular el "límite superior real" sumando el límite superior
// más el valor inicial
cout << "rand() definiendo límite superior, e inferior" << endl;
valor = 1 + rand() % 100; // Genera un valor entre 1 y 100
cout << "1 + rand() % 100: \t" << valor << endl;
valor = 25 + rand() % 100; // Genera un valor entre 25 y 124
cout << "25 + rand() % 100: \t" << valor << endl;
valor = rand() %51; // Genera un valor entre 0 y 50
cout << "rand() %51: \t\t" << valor << endl << endl;
// rand() definiendo límite inferior y especificando el valor del límite superior
// Observa que no dejamos al compilador calcular el lìmite superior "por su cuenta"
cout << "rand() definiendo límite inferior y especificando el valor del límite superior" << endl;
valor = 10 + rand() % (26-10); //Genera un valor entre 10 y 25
cout << "10 + rand() % (26-10): \t" << valor << endl;
valor = 25 + rand() % (100-25); //Genera un valor entre 25 y 100
cout << "25 + rand() % (100-25): " << valor << endl;
return 0;
}
La siguiente es una posible salida del programa, en tu caso seguramente será diferente, aquí es interesante que respondas: ¿Porqué obtienes una salida diferente de otro usuario? No es una pregunta trivial, por si así lo creíste.
Números Aleatorios y Pseudo Aleatorios.
Hasta el momento he utilizado 2 términos sin prestarle mucha atención, es más se utilizan casi de forma indistinta, pero ahora es necesario revisarlos: Números aleatorios y Pseudo aleatorios, para su descripción me apoyo en las definiciones que ofrece Estadística para todos, la cual me parece muy acertada y clara.
Número aleatorio
“Es aquel número obtenido al azar, es decir, que todo número tenga la misma probabilidad de ser elegido y que la elección de uno no dependa de la elección del otro”(1).
Número Pseudo aleatorio
“Son unos números generados por medio de una función (determinista, no aleatoria) y que aparentan ser aleatorios. Estos números pseudo aleatorios se generan a partir de un valor inicial aplicando iterativamente la función” (2).
La naturaleza de los números pseudo aleatorias va ligada a la arquitectura del hardware donde se ejecuta dicha función y de la forma como se implementa la función, de ahí que teóricamente existe la posibilidad de predecir el número que se generará, naturalmente no es una tarea sencilla y se da por hecho que no se puede (pero siempre existe el “tal vez”).
Números pseudo aleatorios en C++
Podrás notar que definimos que la función rand() genera números enteros pseudo aleatorios. Esto significa que los números que se generan con esta función no son “verdaderos números aleatorios”. Lo podrás comprobar si ejecutas varias veces el mismo programa, si lo ejecutas una vez o cien veces, la salida siempre será la misma.
Para efectos de una ejecución podemos considerar que son “lo suficientemente aleatorios”, naturalmente si el programa requiere múltiples iteraciones (como en la generación de contraseñas, por mencionar un caso típico), los números generados no nos serán útiles, pues a partir de la segunda ejecución dejarán de ser aleatorios.
La función srand()
La función srand() inicializa el generador de números aleatorios utilizando el argumento como semilla (valor inicial), la función srand() se utiliza antes de usar rand().
Sintaxis
void srand (unsigned int semilla);
donde,
semilla, valor entero que utiliza como valor inicial (semilla) el algoritmo generador de números pseudo-aleatorios.
Para generar números verdaderamente aleatorios en C++ debemos utilizar una semilla a partir de la cual la función rand() generará los números aleatorios.
// cpp_74_pseudoaleatorio_semilla.cpp
// Se ilustra el uso de la semilla srand al utilizar rand()
// para generar números aleatorios
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Revisa todo el código del tutoral en: https://github.com/carlosgbr/
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int valor1, valor2, valor3;
for (int i=1; i<=3; i++) {
cout << "Corrida: " << i << endl;
valor1 = rand(); // Genera un valor entre 0 y RAND_MAX
cout << "Sin semilla: " << valor1 << endl;
srand(40); // definimos una semilla
valor2 = rand(); // Genera un valor entre 0 y RAND_MAX
cout << "valor2: " << valor2 << endl;
valor3 = rand() % 100 + 1; // Genera un valor entre 1 y 100
cout << "valor3: " << valor3 << endl;
srand(1); // Devolvemos la semilla a su valor inicial
}
return 0;
}
Analizando el comportamiento de srand()
Observa el ejemplo, se añadió la semilla después del primer llamado a rand()
a = rand(); // Genera un valor entre 0 y RAND_MAX
cout << a << endl;
srand(40); // definimos una semilla
a = rand(); // Genera un valor entre 0 y RAND_MAX
cout << a << endl;
Notarás que al ejecutar el programa, a tiene diferente valor, a pesar de estar en el mismo rango y por ejemplos anteriores hemos visto que si no usamos srand() el valor de a sería el mismo en todos. Si comentamos
srand(40); // definimos una semilla
Verás que la salida en las 2 líneas es la misma.
Otra cosa que notarás es que la semilla tiene efecto sobre todas las llamadas a rand() que se encuentran después de la semilla.
Sin embargo notarás nuevamente que si repites la ejecución del programa de forma repetida, las salidas se repiten, para obtener un número distinto hay que modificar la semilla cada vez que se ejecuta el programa. Esto podríamos hacerlo fácilmente solicitando en cada ciclo el valor de la semilla, pero es una solución poco práctica.
rand(), sdrand() y time()
La forma más simple, y efectiva, que tenemos para generar una semilla distinta en cada ejecución es utilizando la función time(), la función time requiere que declares la librería <ctime> en la cabecera del programa. Su función es recuperar la hora del sistema, por lo que al usar este valor como semilla podemos considerar que tenemos una semilla lo suficientemente aleatoria para generar nuestros números aleatorios, dado que en teoría siempre será una semilla distinta.
El siguiente ejemplo ilustra el uso de usa semilla basada en el reloj del sistema, para ello nos valemos del la función time, definida en la librería <ctime>.
// cpp_75_aleatorio_semilla.cpp
// Se ilustra el uso de la semilla srand y la generación de "verdaderos"
// números aleatorios
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Revisa todo el código del tutoral en: https://github.com/carlosgbr/
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <cstdlib> // rand, srand
#include <ctime> // time
using namespace std;
int main()
{
int valor = 0;
srand(time(NULL));
// Simulamos calificaciones en el rango de 5 a 10;
for (int i=1; i<=20; i++) {
valor = 5 + rand()%(11-5);
cout << "Calificación Simulada" << i << ": " << valor << "\t\t\t\t";
if (i%2 == 0) cout << endl;
}
return 0;
}
Al ejecutar el programa podemos ver que contamos con valores, ahora sí, aleatorios, sin importar las veces que realicemos la ejecución del programa.
Un juego de azar.
Terminamos la sección con la implementación típica de juego “adivina el número”, que fácilmente puedes modificar para darte opción a jugar contra otro usuario o contra la máquina (predeterminado), o extendiendo, podrías implementar con el apoyo de arrays, el juego de batalla naval contra la máquina.
// cpp_76_adivinanumero.cpp
// Se implementa un sencillo juego de "Adivina el número, el usuario tiene 2
// oportunidades en un rango de 1 a 10
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Revisa todo el código del tutoral en: https://github.com/carlosgbr/
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <cstdlib> // rand, srand
#include <ctime> // time
using namespace std;
int main()
{
int valorrnd = 0; // Número que debemos adivinar
int valorusr = 0; // Número introducido por el usuario
int contador = 0; // Para registrar el número de iteraciones
srand(time(NULL)); // Generamos semilla basada en el reloj
valorrnd = 1+rand()%10;
cout << "Este programa genera un valor entre 1 y 10, tienes 2 oportunidades" << endl;
cout << "para acertar el número correcto." << endl;
do {
contador += 1;
cout << "Escribe un número entre 1 y 10" << endl;
cin >> valorusr;
if (valorusr == valorrnd) {
cout << "Ganaste!, el número es correcto" << "(" << valorrnd << ")";
break;
} else {
cout << "Lo siento, el número no es ese." << endl << endl;
}
}
while (contador < 2);
if (contador = 2) {
cout << "Perdiste, más suerte para la próxima, el número es: " << valorrnd;
}
return 0;
}
Números aleatorios con decimales en (Linux)
Existen referencias a la generación de números aleatorios decimales, hay que señalar que esta es una característica no estándar y es específica de sistemas Linux. Lo adecuado sería limitar y documentar su uso
Para el tratamiento de estos números compiladores para C++ en Linux nos proporciona 2 funciones: drand48() y srand48(), puedes observar que el nombre incluso es parecido, se adivina fácilmente que hace cada función.
Consideraciones finales
El uso de rand() y srand() son suficientes para la mayoría de los casos, en donde no es crítica la seguridad o donde no se requiere garantizar una “verdadera aleatoriedad” de los números.
Si vas a codificar programas criptográficos, estas funciones no son adecuadas. en su lugar te invito a que revises la documentación de la librería <random> y sus objetos.
También debes tener cuidado al utilizar funciones no estándar (como drand48()), como puede ser el caso de rand_s() implementada por Microsoft, ellos implementan esta función de “Verdaderos números aleatorios” pero es específica de sus compiladores, si vas a portar tu código a otro compilador, deberías evitar su uso. Siempre será preferible hasta donde te sea posible, utilizar las funciones de la librería <random>
Hay mucho más que decir, pero por el momento es suficiente.
Referencias
- (1) (2) Estadística para todos – Números Aleatorios.
- rand en cplusplus.com
- sdrand en cplusplus.com
- rand_s de Microsoft
Fuente Imágenes
- “Todas las demás imágenes de esta sección”: by Nala Systems
Código Fuente
- El código fuente de toda la serie lo puedes descargar en nuestro repositorio en github busca los programas con el nombre del encabezado de cada programa.
- Código de esta sección
Números aleatorios en C++ by Roberto C. González is licensed under a Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.