El problema más fundamental de su aplicación de prueba es que llama srand
una vez y luego llama rand
una vez y sale.
El objetivo de la srand
función es inicializar la secuencia de números pseudoaleatorios con una semilla aleatoria.
Significa que si pasa el mismo valor a srand
dos aplicaciones diferentes (con la misma srand
/ rand
implementación) , obtendrá exactamente la misma secuencia de rand()
valores leídos después de eso en ambas aplicaciones.
Sin embargo, en su aplicación de ejemplo, la secuencia pseudoaleatoria consta solo de un elemento: el primer elemento de una secuencia pseudoaleatoria generada a partir de la inicialización igual al tiempo actual de second
precisión. ¿Qué esperas ver en la salida entonces?
Obviamente, cuando ejecuta la aplicación en el mismo segundo, usa el mismo valor inicial, por lo tanto, su resultado es el mismo (como Martin York ya mencionó en un comentario a la pregunta).
En realidad, debe llamar srand(seed)
una vez y luego llamar rand()
muchas veces y analizar esa secuencia; debe verse al azar.
EDITAR:
Oh ya entiendo. Aparentemente, la descripción verbal no es suficiente (tal vez una barrera del idioma o algo así ... :)).
OKAY. Ejemplo de código C antiguo basado en las mismas srand()/rand()/time()
funciones que se usaron en la pregunta:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ QUE secuencia de una sola ejecución del programa se supone que se vea al azar.
EDIT2:
Cuando se usa la biblioteca estándar C o C ++, es importante comprender que, a partir de ahora, no hay una sola función estándar o clase que produzca datos realmente aleatorios definitivamente (garantizado por el estándar). La única herramienta estándar que aborda este problema es std :: random_device que desafortunadamente aún no ofrece garantías de aleatoriedad real.
Dependiendo de la naturaleza de la aplicación, primero debe decidir si realmente necesita datos verdaderamente aleatorios (impredecibles). Un caso notable cuando realmente necesita una aleatoriedad verdadera es la seguridad de la información, por ejemplo, generar claves simétricas, claves privadas asimétricas, valores de sal, tokens de seguridad, etc.
Sin embargo, los números aleatorios de grado de seguridad son una industria separada que vale un artículo separado.
En la mayoría de los casos , el generador de números pseudoaleatorios es suficiente, por ejemplo, para simulaciones científicas o juegos. En algunos casos, incluso se requiere una secuencia pseudoaleatoria definida de manera consistente, por ejemplo, en los juegos puedes elegir generar exactamente los mismos mapas en tiempo de ejecución para evitar almacenar muchos datos.
La pregunta original y la multitud recurrente de preguntas idénticas / similares (e incluso muchas "respuestas" equivocadas) indican que, ante todo, es importante distinguir los números aleatorios de los números pseudoaleatorios Y comprender qué es una secuencia de números pseudoaleatoria en el primer lugar Y para darse cuenta de que los generadores de números pseudoaleatorios NO se usan de la misma manera que podría usar generadores de números aleatorios verdaderos.
Intuitivamente cuando solicita un número aleatorio: el resultado devuelto no debe depender de los valores devueltos anteriormente y no debe depender si alguien solicitó algo antes y no debería depender en qué momento y por qué proceso y en qué computadora y de qué generador y en qué galaxia fue solicitada. Después de todo, eso es lo que significa la palabra "aleatorio" : ser impredecible e independiente de cualquier cosa; de lo contrario, ya no es aleatorio, ¿verdad? Con esta intuición, es natural buscar en la web algunos hechizos mágicos para obtener ese número aleatorio en cualquier contexto posible.
^^^ ESE tipo de expectativas intuitivas es MUY INCORRECTO y dañino en todos los casos que involucran generadores de números pseudoaleatorios, a pesar de ser razonables para números aleatorios verdaderos.
Si bien existe la noción significativa de "número aleatorio", no existe tal cosa como "número pseudoaleatorio". Un generador de números pseudoaleatorios en realidad produce una secuencia de números pseudoaleatorios .
Cuando los expertos hablan de la calidad de PRNG, en realidad hablan de las propiedades estadísticas de la secuencia generada (y sus notables subsecuencias). Por ejemplo, si combina dos PRNG de alta calidad usándolos ambos por turnos, puede producir una secuencia resultante mala, a pesar de que generan secuencias buenas cada una por separado (esas dos secuencias buenas pueden simplemente correlacionarse entre sí y, por lo tanto, combinarse mal).
De hecho, la secuencia pseudoaleatoria siempre es determinista (predeterminada por su algoritmo y parámetros iniciales), es decir, en realidad no tiene nada de aleatorio.
Específicamente rand()
/ srand(s)
par de funciones proporciona una por proceso singular no thread-safe (!) Secuencia de números pseudo-aleatorio generado con el algoritmo definido por la implementación. La función rand()
produce valores en rango [0, RAND_MAX]
.
Cita del estándar C11:
La srand
función utiliza el argumento como una semilla para una nueva secuencia de números pseudoaleatorios que serán devueltos por llamadas posteriores a rand
. Si
srand
luego se llama con el mismo valor inicial, se repetirá la secuencia de números pseudoaleatorios. Si rand
se llama antes de que srand
se hayan realizado llamadas , se generará la misma secuencia que cuando srand
se llama por primera vez con un valor de inicialización de 1.
Mucha gente espera razonablemente que eso rand()
produzca una secuencia de números semi-independientes distribuidos uniformemente en el rango 0
de RAND_MAX
. Bueno, definitivamente debería (de lo contrario es inútil), pero desafortunadamente no solo el estándar no requiere eso, incluso hay un descargo de responsabilidad explícito que establece que "no hay garantías en cuanto a la calidad de la secuencia aleatoria producida" . En algunos casos históricos rand
/ srand
aplicación era de muy mala calidad por cierto. Aunque en las implementaciones modernas es probable que sea lo suficientemente bueno, pero la confianza está rota y no es fácil de recuperar. Además, su naturaleza no segura para subprocesos hace que su uso seguro en aplicaciones de subprocesos múltiples sea complicado y limitado (aún posible, puede usarlos desde un subproceso dedicado).
La nueva plantilla de clase std :: mersenne_twister_engine <> (y su conveniencia typedefs - std::mt19937
/ std::mt19937_64
con una buena combinación de parámetros de plantilla) proporciona un generador de números pseudoaleatorio por objeto definido en el estándar C ++ 11. Con los mismos parámetros de plantilla y los mismos parámetros de inicialización, diferentes objetos generarán exactamente la misma secuencia de salida por objeto en cualquier computadora en cualquier aplicación creada con una biblioteca estándar compatible con C ++ 11. La ventaja de esta clase es su secuencia de salida predecible de alta calidad y su consistencia total en todas las implementaciones.
También hay más motores PRNG definidos en el estándar C ++ 11: std :: linear_congruential_engine <> (utilizado históricamente como srand/rand
algoritmo de calidad razonable en algunas implementaciones de biblioteca estándar C) y std :: subtract_with_carry_engine <> . También generan secuencias de salida por objeto dependientes de parámetros completamente definidas.
Reemplazo de ejemplo de C ++ 11 moderno para el código C obsoleto anterior:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
La versión del código anterior que usa std :: uniform_int_distribution <>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}