2012-05-02 15 views
5

Avendo bisogno di un generatore di numeri casuali che restituisce un campione da una distribuzione normale (gaussiana), ho portato su F # una porzione di John D. Cook's C# generator:thread-safe normale generatore di numeri casuali in F #

let mutable m_w = 521288629u 
let mutable m_z = 362436069u 

let private getUint() = 
    m_z <- 36969u * (m_z &&& 65535u) + (m_z >>> 16) 
    m_w <- 18000u * (m_w &&& 65535u) + (m_w >>> 16) 
    (m_z <<< 16) + m_w 

let private setSeed() = 
    let dt = System.DateTime.Now 
    let x = dt.ToFileTime() 
    m_w <- uint32 (x >>> 16) 
    m_z <- uint32 (x % 4294967296L) 

let private getUniform() = 
    let u = getUint() 
    (float u + 1.) * 2.328306435454494e-10 

let private randomNormal() = 
    let u1 = getUniform() 
    let u2 = getUniform() 
    let r = sqrt (-2. * (log u1)) 
    let theta = 2. * System.Math.PI * u2 
    r * sin (theta) 

/// Returns a normal (Gaussian) random sample with mean 0 and standard deviation 1 
let randn() = 
    setSeed() 
    randomNormal() 

/// Returns an array of normal (Gaussian) random samples 
let randns n m = 
    setSeed() 
    [| for i in 0 .. n - 1 -> randomNormal() |] 

questa implementazione opere bene, ma non è thread-safe. Dato che il codice che dipende da esso fa ampio uso della Libreria parallela di Thread, ho bisogno di renderlo thread-safe.

Questo non mi sembra ovvio perché al centro del metodo si trovano due membri mutabili che sono praticamente indispensabili. C'è un altro modo per raggiungere la sicurezza del filo senza ricorrere alle serrature?

Esiste un altro modo per implementare un normale generatore pseudo-casuale utilizzando solo membri immutabili?

risposta

5

Utilizzando i membri mutabili, non avresti altra scelta che usare un lucchetto.

Tuttavia, starai meglio con un record immutabile contenente m_w e m_z che passi alle tue funzioni casuali. Potrebbero restituire una tupla del valore casuale e un nuovo record contenente i membri casuali aggiornati. Meglio ancora, è possibile creare un computation expression per gestire i randoms generati in modo da non doversi preoccupare di passare il record casuale in giro.

Inoltre, chiamare setSeed dall'interno delle funzioni casuali non è corretto. Più chiamate successive restituiranno lo stesso valore. Vuoi solo impostare il tuo seme una volta.

+0

Buona chiamata per non chiamare setSeed ogni volta, stupido controllo –

+0

Mi piace questa idea. Potresti per favore elaborare l'opzione di espressione di calcolo (non ho familiarità con il concetto)? –

+0

Assomiglierebbe a qualcosa: 'rnd { let! a = GetNext 10; let! b = GetExponential 2.5; let! c = GetNormal; return a, b, c } ' – IngisKahn

3

Ecco una soluzione thread-safe banale utilizzando System.Random, se si tratta di alcun aiuto:

let random = 
    let rand = System.Random() 
    let locker = obj() 
    fun() -> lock locker rand.Next 
+2

Puoi effettivamente usare questo: http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform. Per convertire da numeri casuali generati da 'System.Random' a numeri distribuiti normalmente. –

3

E 'meglio mettere tutte le m_w/m_z e le relative funzioni in una classe. Come questo:

type Random = 
    let setSeed() = ... 
    let randomNormal() = ... 

Dopo che ci sono almeno due soluzioni: lanciare ogni filo con la propria istanza dell'oggetto Random; oppure utilizzare la classe ThreadLocal<Random> per la stessa cosa: garantire che ogni thread abbia un'istanza della classe Random.

MODIFICA: Inoltre, MailboxProcessor con PostAndReply metodo è un buon modo per condividere un singolo generatore tra i thread. Non c'è bisogno di preoccuparsi della sincronizzazione.

+1

Il problema con la creazione di una nuova istanza ogni volta è che rischiano di ottenere lo stesso seme. Penso che molto probabilmente succederà in uno scenario Array.Parallel.map (fun n -> let r = new Random() ...). –

+2

@FrancescoDeVittori: potresti incorporare dati specifici del thread nel tuo valore di inizializzazione, come 'Thread.CurrentThread.ManagedThreadId'. – Daniel

+0

@FrancescoDeVittori Sì, usare 'DateTime' per il seed iniziale è una cattiva idea. Ma puoi usare 'RNGCryptoServiceProvider.GetBytes' o qualcosa di simile per questo. – qehgt

Problemi correlati