srand(): ¿por qué llamarlo solo una vez?
Esta pregunta es sobre un comentario en esta pregunta ¿
Forma recomendada de inicializar srand? El primer comentario dice que srand()
se debe llamar solo UNA VEZ en una aplicación. ¿Por que es esto entonces?
Eso depende de lo que estés tratando de lograr.
La aleatorización se realiza como una función que tiene un valor inicial, es decir, la semilla .
Entonces, para la misma semilla, siempre obtendrás la misma secuencia de valores.
Si intenta establecer la semilla cada vez que necesita un valor aleatorio y la semilla es el mismo número, siempre obtendrá el mismo valor "aleatorio".
La semilla generalmente se toma de la hora actual, que son los segundos, como en time(NULL)
, por lo que si siempre configura la semilla antes de tomar el número aleatorio, obtendrá el mismo número siempre que llame al combo srand/rand varias veces en el mismo segundo .
Para evitar este problema, srand se configura solo una vez por aplicación, porque es dudoso que dos de las instancias de la aplicación se inicialicen en el mismo segundo, por lo que cada instancia tendrá una secuencia diferente de números aleatorios.
Sin embargo, existe una pequeña posibilidad de que ejecutes tu aplicación (especialmente si es corta, o una herramienta de línea de comandos o algo así) muchas veces en un segundo, entonces tendrás que recurrir a alguna otra forma de elegir una semilla (a menos que usted acepte la misma secuencia en diferentes instancias de aplicación). Pero como dije, eso depende del contexto de uso de su aplicación.
Además, es posible que desee intentar aumentar la precisión a microsegundos (minimizando la posibilidad de que aparezca la misma semilla), requiere ( sys/time.h
):
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
Los números aleatorios son en realidad pseudoaleatorios. Primero se establece una semilla, de la cual cada llamada rand
obtiene un número aleatorio, y modifica el estado interno y este nuevo estado se usa en la siguiente rand
llamada para obtener otro número. Debido a que se utiliza una determinada fórmula para generar estos "números aleatorios", establecer un cierto valor de semilla después de cada llamada devolverá rand
el mismo número de la llamada. Por ejemplo, srand (1234); rand ();
devolverá el mismo valor. Inicializar una vez el estado inicial con el valor inicial generará suficientes números aleatorios ya que no configura el estado interno con srand
, lo que hace que sea más probable que los números sean aleatorios.
Generalmente usamos el time (NULL)
valor de segundos devuelto al inicializar el valor inicial. Digamos que srand (time (NULL));
está en un bucle. Entonces el bucle puede iterarse más de una vez en un segundo, por lo tanto, el número de veces que el bucle itera dentro del bucle en una segunda rand
llamada al bucle devolverá el mismo "número aleatorio", que no es el deseado. Inicializarlo una vez al inicio del programa establecerá la semilla una vez, y cada vez que rand
se llama, se genera un nuevo número y se modifica el estado interno, por lo que la siguiente llamada rand
devuelve un número que es lo suficientemente aleatorio.
Por ejemplo, este código de http://linux.die.net/man/3/rand :
static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
next = next * 1103515245 + 12345;
return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
next = seed;
}
El estado interno next
se declara global. Cada myrand
llamada modificará el estado interno, lo actualizará y devolverá un número aleatorio. Cada llamada myrand
tendrá un next
valor diferente, por lo tanto, el método devolverá números diferentes en cada llamada.
Mire la mysrand
implementación; simplemente establece el valor inicial al que se pasa next
. Por lo tanto, si establece next
el mismo valor cada vez antes de llamar, rand
devolverá el mismo valor aleatorio, debido a la fórmula idéntica aplicada, lo cual no es deseable, ya que la función está hecha para ser aleatoria.
Pero dependiendo de sus necesidades, puede establecer la semilla en un valor determinado para generar la misma "secuencia aleatoria" en cada ejecución, por ejemplo, para algún punto de referencia u otro.
Respuesta corta: llamar nosrand()
es como "tirar los dados" para el generador de números aleatorios. Tampoco es como barajar una baraja de cartas. En todo caso, es más como simplemente cortar una baraja de cartas.
Piensa en esto, de esta manera. rand()
Se trata de una gran baraja de cartas, y cada vez que lo llamas, todo lo que hace es escoger la siguiente carta de la parte superior de la baraja, darte el valor y devolver esa carta a la parte inferior de la baraja. (Sí, eso significa que la secuencia "aleatoria" se repetirá después de un tiempo. Sin embargo, es una baraja muy grande: normalmente 4.294.967.296 cartas).
Además, cada vez que se ejecuta el programa, se compra un nuevo paquete de cartas en la tienda del juego, y cada nuevo paquete de cartas siempre tiene la misma secuencia. Entonces, a menos que haga algo especial, cada vez que se ejecute su programa, obtendrá exactamente los mismos números "aleatorios" de rand()
.
Ahora, podrías decir: "Está bien, entonces, ¿cómo barajo el mazo?" Y la respuesta -al menos en lo que rand
a nosotros srand
respecta- es que no hay forma de barajar las cartas.
Entonces ¿qué srand
hace? Según la analogía que he estado construyendo aquí, llamar srand(n)
es básicamente como decir "corta las n
cartas de la baraja desde arriba". Pero espera, una cosa más: en realidad es comenzar con otro mazo nuevo y cortar las n
cartas desde la parte superior .
Entonces, si llamas a srand(n)
, rand()
, srand(n)
, rand()
, ..., con el mismo n
número cada vez, no solo obtendrás una secuencia no muy aleatoria, sino que en realidad obtendrás el mismo número rand()
cada vez. (Probablemente no sea el mismo número que le entregó srand
, sino el mismo número rand
una y otra vez).
Entonces, lo mejor que puedes hacer es cortar el mazo una vez , es decir, llamar srand()
una vez, al comienzo de tu programa, con un n
valor que sea razonablemente aleatorio, de modo que comiences en un lugar aleatorio diferente en el mazo grande cada vez que tu el programa se ejecuta. Con rand()
, eso es realmente lo mejor que puedes hacer.
[PD: Sí, lo sé, en la vida real, cuando compras una baraja de cartas nueva, normalmente es en orden, no en orden aleatorio. Para que la analogía aquí funcione, me imagino que cada mazo que compras en la tienda de juegos está en un orden aparentemente aleatorio, pero exactamente el mismo orden aparentemente aleatorio que cualquier otro mazo de cartas que compras en esa misma tienda. Algo así como las barajas de cartas barajadas idénticamente que usan en los torneos de bridge.]
Anexo: para ver una demostración muy interesante del hecho de que para un algoritmo PRNG determinado y un valor inicial determinado, siempre se obtiene la misma secuencia, consulte esta pregunta (que trata sobre Java, no C, pero de todos modos).
La razón es que srand()
establece el estado inicial del generador aleatorio, y todos los valores que produce el generador sólo son "suficientemente aleatorios" si no tocas el estado intermedio.
Por ejemplo podrías hacer:
int getRandomValue()
{
srand(time(0));
return rand();
}
y luego, si llama a esa función repetidamente para que time()
devuelva los mismos valores en llamadas adyacentes, simplemente obtendrá el mismo valor generado, eso es por diseño.
srand()
Se ve una solución más sencilla para generar diferentes semillas para instancias de aplicaciones que se ejecutan en el mismo segundo.
srand(time(NULL)-getpid());
Este método hace que su semilla sea muy aleatoria ya que no hay forma de adivinar a qué hora comenzó su hilo y el pid también será diferente.