Generatore di numeri casuali JavaScript selezionabile


150

JavaScript Math.random() funzione restituisce un valore casuale compreso tra 0 e 1, seminato automaticamente in base all'ora corrente (simile a Java credo). Tuttavia, non penso che ci sia modo di impostare il tuo seme per questo.

Come posso creare un generatore di numeri casuali per cui posso fornire il mio valore seed, in modo che possa farlo produrre una sequenza ripetibile di numeri (pseudo) casuali?


1
Nota: nell'interesse di mantenere questa domanda breve e focalizzata, ho diviso il codice che era nella domanda sopra a una risposta Wiki della comunità di seguito.
Ilmari Karonen,

Risposte:


124

Un'opzione è http://davidbau.com/seedrandom che è un rimpiazzo drop-in Math.random () basato su RC4 con belle proprietà.


18
Il seedrandom di David Bau da allora è diventato abbastanza popolare da mantenerlo qui su Github . È un peccato che ECMAScript sia stato così a lungo in scena per così tanto tempo che cose come questa non sono incluse nella lingua. Scherzi a parte, nessuna semina !!!
Mangia da Joes il

2
@EatatJoes, È sia la vergogna che la gloria di JS che ciò sia necessario e possibile. È piuttosto interessante poter includere un file e ottenere modifiche compatibili all'indietro apportate all'oggetto Math. Non male per 10 giorni di lavoro, Brendan Eich.
Bruno Bronosky,

2
Per chiunque cerchi la pagina npm per questo progetto: npmjs.com/package/seedrandom
Kip

27

Se non hai bisogno della capacità di seeding, usa Math.random()e costruisci attorno le funzioni di supporto (ad es.randRange(start, end) ).

Non sono sicuro di quale RNG stai usando, ma è meglio conoscerlo e documentarlo in modo da essere consapevole delle sue caratteristiche e limitazioni.

Come Starkii ha detto, Mersenne Twister è un buon PRNG, ma non è facile da implementare. Se vuoi farlo da solo, prova a implementare un LCG : è molto semplice, ha qualità di casualità decenti (non buone come Mersenne Twister) e puoi usare alcune delle costanti popolari.

EDIT: considera le grandi opzioni a questa risposta per le implementazioni di RNG seminabili brevi, inclusa un'opzione LCG.

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));


2
Il modulo non dovrebbe essere 2 ^ 31? Ho letto questo algoritmo dal wiki .
Trantor Liu

3
Solo per quello che sai, questo non è "corretto", nel senso che non produce ciò che la matematica impone. In altre parole, una lingua in grado di gestire quei numeri di grandi dimensioni avrà un risultato diverso. JS soffoca sui grandi numeri e riduce la precisione (dopo tutto sono galleggianti).
DDS

4
-1 Questa implementazione di LCG supera il limite per numeri interi esatti in JavaScript poiché this.a * this.stateè probabile che comporti un numero maggiore di 2 ^ 53. Il risultato è un intervallo di uscita limitato e per alcuni semi probabilmente un periodo molto breve. Inoltre, in generale, usando una potenza di due per mottenere alcuni schemi abbastanza ovvi, quando si sta spendendo un'operazione di modulo piuttosto che un semplice troncamento, non c'è motivo di non usare un numero primo.
aaaaaaaaaaaa

22

Se vuoi essere in grado di specificare il seed, devi solo sostituire le chiamate a getSeconds()e getMinutes(). Puoi passare un int e usarne metà mod 60 per il valore dei secondi e l'altra metà modulo 60 per darti l'altra parte.

Detto questo, questo metodo sembra spazzatura. Fare una corretta generazione casuale di numeri è molto difficile. Il problema ovvio con questo è che il seme del numero casuale si basa su secondi e minuti. Per indovinare il seme e ricreare il tuo flusso di numeri casuali richiede solo di provare 3600 diverse combinazioni di secondi e minuti. Significa anche che ci sono solo 3600 diversi semi possibili. Questo è correggibile, ma sarei sospettoso di questo RNG sin dall'inizio.

Se vuoi usare un RNG migliore, prova Mersenne Twister . È un RNG ben collaudato e abbastanza robusto con un'orbita enorme e prestazioni eccellenti.

EDIT: dovrei davvero essere corretto e fare riferimento a questo come un generatore di numeri pseudo casuali o PRNG.

"Chiunque usi metodi aritmetici per produrre numeri casuali è in uno stato di peccato."
                                                                                                                                                          --- John von Neumann


1
Un collegamento alle implementazioni JS di Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip,

1
@orip Hai una fonte per i 3600 stati iniziali? Mersenne Twister è seeded con un numero a 32 bit, quindi il PRNG dovrebbe avere 4 miliardi di stati iniziali - solo se il seed iniziale è veramente casuale.
Tobias P.

2
@TobiasP. Mi riferivo al suggerimento di eseguire il seeding con una combinazione di getSeconds () e getMinutes (), 60 * 60 == 3600 possibili stati iniziali. Non mi riferivo a Mersenne Twister.
Orip

3
@orip Ok, non era chiaro. Stavi prendendo in giro Mersenne Twister e nella frase successiva sugli stati iniziali;)
Tobias P.

2
L'interrogativo non fa menzione del fatto che hanno bisogno di una "corretta" generazione di numeri casuali per qualsiasi tipo di applicazione crittograficamente sensibile. Mentre tutta la risposta è vera, solo il primo paragrafo è effettivamente rilevante per la domanda posta. Forse aggiungi uno snippet di codice della soluzione suggerita.
V. Rubinetti,


8

Il codice che hai elencato sembra un Lehmer RNG . In questo caso, allora 2147483647è il numero intero con 2147483647segno a 32 bit più grande , è il numero primo a 32 bit più grande ed 48271è un moltiplicatore a periodo intero che viene utilizzato per generare i numeri.

Se questo è vero, è possibile modificare RandomNumberGeneratorper accettare un parametro aggiuntivo seede quindi impostare this.seedsu seed; ma dovresti fare attenzione per assicurarti che il seme si traduca in una buona distribuzione di numeri casuali (Lehmer può essere strano così) - ma la maggior parte dei semi andrà bene.


7

Di seguito è riportato un PRNG che può essere alimentato con un seme personalizzato. La chiamata SeedRandomrestituirà una funzione di generatore casuale. SeedRandompuò essere chiamato senza argomenti per eseguire il seeding della funzione casuale restituita con l'ora corrente, oppure può essere chiamato con 1 o 2 interse non negativi come argomenti per eseguire il seeding con tali numeri interi. A causa della precisione del virgola mobile, il seeding con solo 1 valore consentirà di avviare il generatore solo in uno di 2 ^ 53 stati diversi.

La funzione generatore casuale restituita accetta 1 argomento intero denominato limit, il limite deve essere compreso nell'intervallo da 1 a 4294965886, la funzione restituirà un numero compreso nell'intervallo da 0 a limite-1.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

Esempio di utilizzo:

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

Questo generatore presenta le seguenti proprietà:

  • Ha circa 2 ^ 64 diversi stati interni possibili.
  • Ha un periodo di circa 2 ^ 63, molto più di quanto chiunque abbia mai realisticamente bisogno in un programma JavaScript.
  • Dato che i modvalori sono numeri primi non c'è un semplice schema nell'output, indipendentemente dal limite scelto. Questo è diverso da alcuni PRNG più semplici che presentano alcuni schemi abbastanza sistematici.
  • Elimina alcuni risultati per ottenere una distribuzione perfetta, indipendentemente dal limite.
  • È relativamente lento, corre circa 10.000.000 di volte al secondo sulla mia macchina.

2
Perché questo produce uno schema? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski,

@TimothyKanski Perché lo stai usando in modo sbagliato. Non sono un esperto, ma questo accade perché stai inizializzando il generatore su ogni iterazione, vedendo solo il suo primo valore basato sul seme e NON iterando sui numeri successivi del generatore. Credo che ciò accadrà in qualsiasi PRNG che non esegue il hash del seme nell'intervallo specificato.
bryc,

5

Se programmi in Typescript, ho adattato l'implementazione di Mersenne Twister che ha portato la risposta di Christoph Henkelmann a questa discussione come una classe dattiloscritta:

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.mti;
            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
            /* In the previous versions, MSBs of the seed affect   */
            /* only MSBs of the array mt[].                        */
            /* 2002/01/09 modified by Makoto Matsumoto             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

            if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                this.init_genrand(5489);
            /* a default initial seed is used */

            for (kk = 0; kk < Random.N - Random.M; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            for (; kk < Random.N - 1; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
            this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];

            this.mti = 0;
        }

        y = this.mt[this.mti++];

        /* Tempering */
        y ^= (y >>> 11);
        y ^= (y << 7) & 0x9d2c5680;
        y ^= (y << 15) & 0xefc60000;
        y ^= (y >>> 18);

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

puoi usarlo come segue:

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

controlla la fonte per altri metodi.


0

Nota: questo codice è stato originariamente incluso nella domanda sopra. Nell'interesse di mantenere la domanda breve e focalizzata, l'ho spostata in questa risposta Wiki della community.

Ho trovato questo codice in movimento e sembra funzionare bene per ottenere un numero casuale e quindi utilizzare il seme in seguito, ma non sono sicuro di come funzioni la logica (ad esempio, da dove provengono i numeri 2345678901, 48271 e 2147483647).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/

3
Wow, le funzioni RandomNumberGeneratore nextRandomNumberrisalgono addirittura al 1996. Dovrebbe essere un Lehmer / LCG RNG. Utilizza alcuni calcoli matematici intelligenti per eseguire l'aritmetica del modulo su numeri interi a 32 bit che altrimenti sarebbero troppo piccoli per contenere alcuni valori intermedi. Il fatto è che JavaScript non implementa numeri interi a 32 bit, ma piuttosto float a 64 bit, e poiché la divisione non è divisione intera come questo codice presume che il risultato non sia un generatore Lehmer. Produce alcuni risultati che sembrano casuali, ma le garanzie di un generatore Lehmer non si applicano.
aaaaaaaaaaaa

1
La createRandomNumberfunzione è un'aggiunta successiva, fa praticamente tutto di sbagliato, in particolare crea un'istanza di un nuovo RNG ogni volta che viene chiamata, il che significa che le chiamate in rapida successione useranno tutte lo stesso float. Nel codice dato è quasi impossibile 'a'essere accoppiati con qualsiasi cosa tranne '1'e 'red'.
aaaaaaaaaaaa

-2

OK, ecco la soluzione su cui ho optato.

Per prima cosa crei un valore seed usando la funzione "newseed ()". Quindi si passa il valore seed alla funzione "srandom ()". Infine, la funzione "srandom ()" restituisce un valore pseudo casuale compreso tra 0 e 1.

Il bit cruciale è che il valore seed è archiviato all'interno di un array. Se fosse semplicemente un numero intero o float, il valore verrebbe sovrascritto ogni volta che veniva chiamata la funzione, poiché i valori di numeri interi, float, stringhe e così via vengono memorizzati direttamente nello stack rispetto ai soli puntatori come nel caso di matrici e altri oggetti. Pertanto, è possibile che il valore del seme rimanga persistente.

Infine, è possibile definire la funzione "srandom ()" in modo tale che sia un metodo dell'oggetto "Math", ma lo lascerò a te capire. ;)

In bocca al lupo!

JavaScript:

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

Lua 4 (il mio ambiente di destinazione personale):

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

PS: non ho ancora familiarità con Stack Overflow, ma perché i post non sono in ordine cronologico?
posfan12,

Ciao @ posfan12 - le risposte alle domande sono in genere elencate in ordine di "voti positivi" in modo tale che la "crema sale verso l'alto". Tuttavia, per garantire una corretta visualizzazione delle risposte con lo stesso punteggio, vengono visualizzate in ordine casuale. Dal momento che questa era la mia domanda in origine ;-) Sarò sicuramente sicuro di dare un'occhiata a breve. Se io (o altri) trovo utile questa risposta, la voteremo e se trovo che sia la risposta "corretta", vedrai anche un segno di spunta verde aggiunto a questa risposta. - Benvenuto in StackOverflow!
scunliffe,

2
-1 Questa implementazione di LCG supera il limite per numeri interi esatti in JavaScript poiché seedobj[0] * seedobjaè probabile che comporti un numero maggiore di 2 ^ 53. Il risultato è un intervallo di uscita limitato e per alcuni semi probabilmente un periodo molto breve.
aaaaaaaaaaaa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.