it-swarm.com.de

Startbarer JavaScript-Zufallszahlengenerator

Die JavaScript Math.random() -Funktion gibt einen zufälligen Wert zwischen 0 und 1 zurück, der basierend auf der aktuellen Zeit automatisch gesetzt wird (ähnlich wie bei Java glaube ich)). Ich glaube jedoch nicht, dass es eine Möglichkeit gibt, einen eigenen Keim dafür zu setzen.

Wie kann ich einen Zufallszahlengenerator erstellen, für den ich meinen eigenen Startwert bereitstellen kann, damit er eine wiederholbare Folge von (Pseudo-) Zufallszahlen erzeugt?

133
scunliffe

Eine Option ist http://davidbau.com/seedrandom . Dies ist ein aussagekräftiger RC4-basierter Math.random () -Ersatz mit Nice-Eigenschaften.

111
David Bau

wenn Sie die Seeding-Funktion nicht benötigen, verwenden Sie einfach Math.random() und erstellen Sie Hilfsfunktionen (z. B. randRange(start, end)).

Ich bin nicht sicher, welches RNG Sie verwenden, aber es ist am besten, es zu kennen und zu dokumentieren, damit Sie sich seiner Eigenschaften und Einschränkungen bewusst sind.

Wie Starkii sagte, ist Mersenne Twister ein gutes PRNG, aber es ist nicht einfach zu implementieren. Wenn Sie es selbst tun möchten, implementieren Sie ein LCG - es ist sehr einfach, hat anständige Zufälligkeitsqualitäten (nicht so gut wie Mersenne Twister) und Sie können es verwenden einige der populären Konstanten.

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));
23
orip

Wenn Sie den Startwert angeben möchten, müssen Sie nur die Aufrufe von getSeconds() und getMinutes() ersetzen. Sie könnten ein int übergeben und die Hälfte von mod 60 für den Sekundenwert und die andere Hälfte von modulo 60 verwenden, um Ihnen den anderen Teil zu geben.

Abgesehen davon sieht diese Methode wie Müll aus. Die richtige Erzeugung von Zufallszahlen ist sehr schwierig. Das offensichtliche Problem dabei ist, dass der Zufallszahlen-Startwert auf Sekunden und Minuten basiert. Um den Startwert zu erraten und Ihren Zufallszahlenstrom wiederherzustellen, müssen Sie lediglich 3600 verschiedene Sekunden- und Minutenkombinationen ausprobieren. Dies bedeutet auch, dass es nur 3600 verschiedene mögliche Samen gibt. Dies ist korrigierbar, aber ich wäre von Anfang an misstrauisch gegenüber diesem RNG.

Wenn Sie ein besseres RNG verwenden möchten, versuchen Sie es mit Mersenne Twister . Es ist ein gut getestetes und ziemlich robustes RNG mit einer großen Umlaufbahn und hervorragender Leistung.

EDIT: Ich sollte wirklich korrekt sein und dies als Pseudo-Zufallszahlengenerator oder PRNG bezeichnen.

"Wer mit arithmetischen Methoden Zufallszahlen erzeugt, befindet sich in einem Zustand der Sünde."
--- John von Neumann

22
Starkii

Ich verwende einen JavaScript-Port des Mersenne Twister: https://Gist.github.com/300494 Hier können Sie den Startwert manuell einstellen. Wie in anderen Antworten erwähnt, ist der Mersenne Twister auch ein wirklich guter PRNG.

11

Der von Ihnen aufgeführte Code sieht aus wie ein Lehmer RNG . Wenn dies der Fall ist, dann 2147483647 ist die größte 32-Bit-Ganzzahl mit Vorzeichen, 2147483647 ist die größte 32-Bit-Primzahl, und 48271 ist ein Ganzperioden-Multiplikator, der zum Generieren der Zahlen verwendet wird.

Wenn dies zutrifft, können Sie RandomNumberGenerator ändern, um einen zusätzlichen Parameter seed aufzunehmen, und dann this.seed bis seed; Sie müssen jedoch vorsichtig sein, um sicherzustellen, dass der Samen eine gute Verteilung der Zufallszahlen ergibt (Lehmer kann so komisch sein) - aber die meisten Samen werden in Ordnung sein.

8
mipadi

Das Folgende ist ein PRNG, dem ein benutzerdefinierter Startwert zugewiesen werden kann. Das Aufrufen von SeedRandom gibt eine Zufallsgeneratorfunktion zurück. SeedRandom kann ohne Argumente in der angegebenen Reihenfolge aufgerufen werden Um die zurückgegebene Zufallsfunktion mit der aktuellen Zeit zu sortieren, oder um sie mit diesen ganzen Zahlen zu sortieren, kann sie entweder mit 1 oder 2 nicht-negativen Inters als Argumente aufgerufen werden Generator auf einen von 2 ^ 53 verschiedenen Zuständen zu initiieren.

Die zurückgegebene Zufallsgeneratorfunktion verwendet ein ganzzahliges Argument mit dem Namen limit. Das Limit muss im Bereich von 1 bis 4294965886 liegen. Die Funktion gibt eine Zahl im Bereich von 0 bis limit-1 zurück.

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
}

Beispiel Verwendung:

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.

Dieser Generator weist folgende Eigenschaften auf:

  • Es hat ungefähr 2 ^ 64 verschiedene mögliche innere Zustände.
  • Es hat einen Zeitraum von ungefähr 2 ^ 63, viel mehr als irgendjemand jemals in einem JavaScript-Programm benötigt.
  • Da die mod Werte Primzahlen sind, enthält die Ausgabe kein einfaches Muster, unabhängig von der gewählten Grenze. Dies ist anders als bei einigen einfacheren PRNGs, die recht systematische Muster aufweisen.
  • Es werden einige Ergebnisse verworfen, um eine perfekte Verteilung zu erhalten, unabhängig von der Grenze.
  • Es ist relativ langsam und läuft ungefähr 10 000 000 Mal pro Sekunde auf meiner Maschine.
7
aaaaaaaaaaaa

Wenn Sie in TypeScript programmieren, habe ich die Mersenne Twister-Implementierung, die in Christoph Henkelmanns Antwort auf diesen Thread enthalten war, als TypeScript-Klasse angepasst:

/**
 * 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);
    }
}

sie können es dann wie folgt verwenden:

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]

überprüfen Sie die Quelle für weitere Methoden.

4
bennyl

Hinweis: Dieser Code war ursprünglich in der obigen Frage enthalten. Um die Frage kurz und fokussiert zu halten, habe ich sie in diese Community-Wiki-Antwort verschoben.

Ich habe festgestellt, dass dieser Code herumwirbelt, und es scheint gut zu funktionieren, um eine Zufallszahl zu erhalten und anschließend den Startwert zu verwenden, aber ich bin nicht ganz sicher, wie die Logik funktioniert (z. B. wo die Zahlen 2345678901, 48271 und 2147483647 herkommen).

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.
*/
0
Ilmari Karonen