Il problema fondamentale dell'applicazione di prova è che si chiama srand
una volta e poi si chiama rand
una volta e si esce.
L'intero punto della srand
funzione è inizializzare la sequenza di numeri pseudo-casuali con un seme casuale.
Ciò significa che se si passa lo stesso valore per srand
due diverse applicazioni (con lo stesso srand
/ rand
attuazione) allora si ottiene esattamente la stessa sequenza di rand()
valori letti dopo che in entrambe le applicazioni.
Tuttavia, nella tua applicazione di esempio, la sequenza pseudo-casuale è costituita da un solo elemento: il primo elemento di una sequenza pseudo-casuale generata da seme uguale al tempo corrente di second
precisione. Cosa ti aspetti di vedere in uscita allora?
Ovviamente quando ti capita di eseguire l'applicazione nello stesso secondo - usi lo stesso valore seed - quindi il tuo risultato è lo stesso ovviamente (come già menzionato Martin York in un commento alla domanda).
In realtà dovresti chiamare srand(seed)
una volta e poi chiamare rand()
più volte e analizzare quella sequenza: dovrebbe apparire casuale.
MODIFICARE:
Ah, ho capito. Una descrizione apparentemente verbale non è sufficiente (forse una barriera linguistica o qualcosa del genere ... :)).
OK. Esempio di codice C vecchio stile basato sulle stesse srand()/rand()/time()
funzioni utilizzate nella domanda:
#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;
}
^^^ QUESTA sequenza da una singola esecuzione del programma dovrebbe apparire casuale.
EDIT2:
Quando si utilizza la libreria standard C o C ++ è importante comprendere che al momento non esiste una singola funzione standard o classe che produce dati effettivamente casuali definitivamente (garantiti dallo standard). L'unico strumento standard che affronta questo problema è std :: random_device che purtroppo non fornisce ancora garanzie di casualità effettiva.
A seconda della natura dell'applicazione, dovresti prima decidere se hai davvero bisogno di dati veramente casuali (imprevedibili). Un caso degno di nota quando si ha sicuramente bisogno della vera casualità è la sicurezza delle informazioni, ad esempio la generazione di chiavi simmetriche, chiavi private asimmetriche, valori salt, token di sicurezza, ecc.
Tuttavia, i numeri casuali di livello di sicurezza sono un settore separato che vale un articolo separato.
Nella maggior parte dei casi è sufficiente un generatore di numeri pseudo-casuali , ad esempio per simulazioni o giochi scientifici. In alcuni casi è persino necessaria una sequenza pseudo-casuale definita in modo coerente, ad esempio nei giochi è possibile scegliere di generare esattamente le stesse mappe in fase di esecuzione per evitare di archiviare molti dati.
La domanda originale e la moltitudine ricorrente di domande identiche / simili (e anche molte "risposte" sbagliate a queste) indicano che è innanzitutto importante distinguere i numeri casuali dai numeri pseudo-casuali E capire cos'è la sequenza numerica pseudo-casuale in il primo posto E rendersi conto che i generatori di numeri pseudo-casuali NON sono usati allo stesso modo in cui si potrebbero usare generatori di numeri casuali veri.
Intuitivamente quando richiedi un numero casuale - il risultato restituito non dovrebbe dipendere dai valori precedentemente restituiti e non dovrebbe dipendere se qualcuno ha richiesto qualcosa prima e non dovrebbe dipendere in quale momento e da quale processo e su quale computer e da quale generatore e in quale galassia è stata richiesta. Questo è ciò che significa "casuale" dopo tutto - essere imprevedibile e indipendente da qualsiasi cosa - altrimenti non è più casuale, giusto? Con questa intuizione è naturale cercare sul web alcuni incantesimi da lanciare per ottenere un numero così casuale in qualsiasi contesto possibile.
^^^ Quel tipo di aspettative intuitive È MOLTO SBAGLIATO e dannoso in tutti i casi che coinvolgono generatori di numeri pseudo-casuali - nonostante sia ragionevole per numeri casuali reali.
Mentre esiste la nozione significativa di "numero casuale", non esiste un "numero pseudo-casuale". Un generatore di numeri pseudo-casuali produce effettivamente una sequenza numerica pseudo-casuale .
Quando gli esperti parlano della qualità del PRNG, in realtà parlano delle proprietà statistiche della sequenza generata (e delle sue notevoli sub-sequenze). Ad esempio, se si combinano due PRNG di alta qualità utilizzandoli entrambi a turno - è possibile che si producano cattive sequenze risultanti - nonostante generino buone sequenze ciascuna separatamente (quelle due buone sequenze possono semplicemente correlarsi tra loro e quindi combinarsi male).
La sequenza pseudo-casuale è infatti sempre deterministica (predeterminata dal suo algoritmo e dai suoi parametri iniziali), cioè in realtà non c'è nulla di casuale al riguardo.
In particolare rand()
/ srand(s)
coppia di funzioni fornisce una sequenza numerica pseudo-casuale (!) Singolare per processo non thread-safe (!) Generata con algoritmo definito dall'implementazione. La funzione rand()
produce valori nell'intervallo [0, RAND_MAX]
.
Citazione dalla norma C11:
La srand
funzione utilizza l'argomento come seed per una nuova sequenza di numeri pseudo-casuali a cui restituire le chiamate successive rand
. Se
srand
viene quindi chiamato con lo stesso valore di seed, la sequenza di numeri pseudo-casuali deve essere ripetuta. Se rand
viene chiamato prima che srand
sia stata effettuata qualsiasi chiamata , deve essere generata la stessa sequenza di quando srand
viene chiamata per la prima volta con un valore seed di 1.
Molte persone ragionevolmente si aspettano che rand()
produrrebbe una sequenza di numeri uniformemente distribuiti semi-indipendenti nella gamma 0
a RAND_MAX
. Beh, sicuramente dovrebbe (altrimenti è inutile) ma sfortunatamente non solo lo standard non lo richiede - c'è persino un esplicito disclaimer che afferma che "non vi sono garanzie sulla qualità della sequenza casuale prodotta" . In alcuni casi storici rand
/ srand
implementazione era di pessima qualità davvero. Anche se nelle implementazioni moderne è molto probabilmente abbastanza buono, ma la fiducia è rotta e non è facile da recuperare. Oltre alla sua natura non thread-safe, il suo utilizzo sicuro in applicazioni multi-thread è complicato e limitato (ancora possibile - è possibile utilizzarli solo da un thread dedicato).
Il nuovo modello di classe std :: mersenne_twister_engine <> (e la sua convenienza typedefs - std::mt19937
/ std::mt19937_64
con una buona combinazione di parametri del modello) fornisce un generatore di numeri pseudo-casuali per oggetto definito nello standard C ++ 11. Con gli stessi parametri del modello e gli stessi parametri di inizializzazione, oggetti diversi genereranno esattamente la stessa sequenza di output per oggetto su qualsiasi computer in qualsiasi applicazione costruita con libreria standard conforme a C ++ 11. Il vantaggio di questa classe è la sua prevedibile sequenza di output di alta qualità e la piena coerenza tra le implementazioni.
Inoltre ci sono più motori PRNG definiti nello standard C ++ 11 - std :: linear_congruential_engine <> (storicamente usato come srand/rand
algoritmo di qualità equa in alcune implementazioni di librerie standard C) e std :: subtract_with_carry_engine <> . Inoltre, generano sequenze di output per oggetto completamente definite e dipendenti da parametri.
Sostituzione di esempio C ++ 11 dei giorni nostri per il codice C obsoleto sopra:
#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 versione del codice precedente che utilizza 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;
}