2009-06-10 10 views
5

Ho visto un bel paio di raccomandazioni per non semina numeri pseudo-casuali generatori più di una volta per l'esecuzione, ma mai accompagnato da una spiegazione approfondita. Naturalmente, è facile capire perché il seguente (C/C++) esempio non è una buona idea:Problemi con il seeding di un generatore di numeri pseudo-casuali più di una volta?

int get_rand() { 
    srand(time(NULL)); 
    return rand(); 
} 

dal chiamando get_rand più volte al secondo produce ripetuti risultati.

Ma non sarebbe il seguente esempio ancora essere una soluzione accettabile?

MyRand.h

#ifndef MY_RAND_H 
#define MY_RAND_H 

class MyRand 
{ 
    public: 
    MyRand(); 
    int get_rand() const; 
    private: 
    static unsigned int seed_base; 
}; 

#endif 

MyRand.cpp

#include <ctime> 
#include <cstdlib> 
#include "MyRand.h" 

unsigned int MyRand::seed_base = static_cast<unsigned int>(time(NULL)); 

MyRand::MyRand() 
{ 
    srand(seed_base++); 
} 

int MyRand::get_rand() const 
{ 
    return rand(); 
} 

main.cpp

#include <iostream> 
#include "MyRand.h" 

int main(int argc, char *argv[]) 
{ 
    for (int i = 0; i < 100; i++) 
    { 
    MyRand r; 
    std::cout << r.get_rand() << " "; 
    } 
} 

, anche se il costruttore MyRand: s viene chiamato più volte in rapida successione, ogni chiamata a srand ha un parametro diverso. Ovviamente, questo non è thread-safe, ma poi ancora nessuno dei due è rand.

+0

Potrei aggiungere che l'intero scopo di questo esercizio è di alleviare il "peso" di chiamare srand dal client di 'MyRand', dove' MyRand' potrebbe modellare un dado. Ma d'altra parte, se costruiamo anche le ruote della fortuna, i lanci di monete ecc. Allo stesso modo, otterremo molti semi. – a038c56f

risposta

6

Ogni volta che si chiama una funzione di generatore di numeri pseudo-casuali, il generatore richiede qualche stato interno e produce un numero pseudo-casuale e un nuovo stato interno. L'algoritmo per la trasformazione dello stato interno viene scelto con attenzione in modo che l'output appaia casuale.

Quando si inizializzare il generatore di numeri casuali, il gioco è fondamentalmente l'impostazione di questo stato interno. Se si ripristina lo stato interno su un valore prevedibile, si perderà l'aspetto della casualità.

Ad esempio, un popolare, semplice RNG è un generatore lineare congruenziale. I numeri vengono generati in questo modo:

X[n+1] = (a X[n] + c) mod m 

In questo caso, X [n + 1] è sia il risultato e il nuovo stato interno.Se si inizializzare il generatore ogni volta come lei suggerisce sopra, si otterrà una sequenza che assomiglia a questo:

{(ab + c) mod m, (a(b+1) + c) mod m, (a(b+2) + c) mod m, ...} 

dove b è la tua seed_base. Questo non sembra affatto casuale.

+0

Il mio esempio main.cpp è un po 'esagerato allo scopo di dimostrare la mancanza del difetto del primo esempio. Istanziare un nuovo oggetto per ogni chiamata a 'get_rand' porterà alla situazione che descrivi sopra, ma questa è solo una programmazione dispendiosa. Supponendo che il numero di istanze di 'MyRand' siano poche rispetto alle chiamate di' get_rand', le cose sembrano un po 'meglio. – a038c56f

1

Se il seme è prevedibile, che è qui da quando sei solo incrementandolo, l'uscita da rand() sarà anche prevedibile.

In realtà dipende il motivo per cui si desidera generare numeri casuali, e in che modo "random" è un accettabile casuale per te. Nel tuo esempio, potrebbe evitare duplicati in rapida successione e ciò potrebbe essere sufficiente per te. Dopotutto, quello che conta è che funzioni.

Su quasi ogni piattaforma c'è un modo migliore per generare numeri casuali di rand().

1

Beh, è ​​elaborazione aggiuntiva che non ha bisogno di essere fatto.

In questo scenario avevo appena chiamare il costruttore una volta con un seme basato sul tempo prima dell'inizio del ciclo. Ciò garantirà risultati casuali senza il sovraccarico extra di cambiare i semi per ogni iterazione.

Non credo che il tuo metodo sia uno qualsiasi più casuale.

0

Si può pensare alla generazione di numeri casuali (questo non è più strettamente vero per l'implementazione, ma serve come illustrazione) come una tabella di valori. Se ricordi di aver fatto qualcosa di questo in statistica per fare semplici campioni casuali, un seme ti dice in sostanza quale riga e colonna iniziare nella tua grande tabella di numeri casuali. Rianimare continuamente è semplicemente inutile poiché possiamo già presumere che i numeri siano già distribuiti normalmente.

Semplicemente non vi è alcun vantaggio aggiunto alla semina più di una volta poiché questo dovrebbe essere sufficiente (a seconda dell'applicazione). Se hai bisogno di "più" numeri casuali, ci sono molti metodi per generare numeri casuali. Un caso che posso pensare è quello di generare numeri casuali in un modo sicuro per i thread.

Mentre la soluzione è accettabile, i numeri non saranno più casuali rispetto alla semina una volta, a livello globale. srand generalmente non dovrebbe appartenere a un costruttore. Se desideri supportare numeri casuali, semina una volta all'avvio del programma e dimenticatene.

Problemi correlati