web-dev-qa-db-fra.com

Générateur de nombres aléatoires JavaScript

La fonction JavaScript Math.random() renvoie une valeur aléatoire comprise entre 0 et 1, générée automatiquement en fonction de l'heure actuelle (similaire à Java je crois). Cependant, je ne pense pas qu'il y ait un moyen de vous créer votre propre graine.

Comment puis-je créer un générateur de nombres aléatoires pour lequel je peux fournir ma propre valeur de départ, afin que je puisse le faire produire une séquence répétable de nombres (pseudo) aléatoires?

133
scunliffe

Une option est http://davidbau.com/seedrandom , qui est un remplaçant insérable de Math.random () basé sur RC4 avec des propriétés Nice.

111
David Bau

si vous n'avez pas besoin de la capacité de distribution, utilisez simplement Math.random() et créez des fonctions d'assistance autour de celle-ci (par exemple, randRange(start, end)).

Je ne suis pas sûr du type de GNA que vous utilisez, mais il est préférable de le connaître et de le documenter afin de connaître ses caractéristiques et ses limites.

Comme Starkii l'a dit, Mersenne Twister est un bon PRNG, mais il n'est pas facile à mettre en œuvre. Si vous voulez le faire vous-même, essayez de mettre en œuvre un LCG - c'est très facile, présente des qualités aléatoires décentes (pas aussi bon que Mersenne Twister), et vous pouvez utiliser certaines des constantes populaires.

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

Si vous voulez pouvoir spécifier le germe, il vous suffit de remplacer les appels à getSeconds() et getMinutes(). Vous pouvez passer dans un int et utiliser la moitié de celui-ci mod 60 pour la valeur des secondes et l'autre moitié modulo 60 pour vous donner l'autre partie.

Cela étant dit, cette méthode ressemble à une poubelle. Faire la génération de nombres aléatoires appropriée est très difficile. Le problème évident avec ceci est que la graine de nombre aléatoire est basée sur les secondes et les minutes. Pour deviner la graine et recréer votre flot de nombres aléatoires, il suffit d'essayer 3600 combinaisons différentes de secondes et de minutes. Cela signifie également qu'il n'y a que 3 600 semences possibles. C'est corrigible, mais je me méfierais de ce RNG dès le début.

Si vous voulez utiliser un meilleur RNG, essayez le Mersenne Twister . C'est un RNG bien testé et assez robuste avec une orbite énorme et une excellente performance.

EDIT: Je devrais vraiment être correct et se référer à cela comme un générateur de nombre pseudo-aléatoire ou PRNG.

"Quiconque utilise des méthodes arithmétiques pour produire des nombres aléatoires est en état de péché."
--- John von Neumann

22
Starkii

J'utilise un portage JavaScript du Mersenne Twister: https://Gist.github.com/300494 Il vous permet de définir le germe manuellement. En outre, comme mentionné dans d'autres réponses, le Mersenne Twister est un très bon PRNG.

11

Le code que vous avez énuméré ressemble à un Lehmer RNG . Si tel est le cas, alors 2147483647 est le plus grand entier signé 32 bits, 2147483647 est le plus grand nombre premier sur 32 bits et 48271 est un multiplicateur de période complète utilisé pour générer les nombres.

Si cela est vrai, vous pouvez modifier RandomNumberGenerator pour intégrer un paramètre supplémentaire seed, puis définir this.seed à seed; mais vous devez faire attention à vous assurer que la graine donnera une bonne distribution de nombres aléatoires (Lehmer peut être bizarre comme ça) - mais la plupart des graines iront bien.

8
mipadi

Ce qui suit est un PRNG) qui peut être alimenté par une graine personnalisée. L'appel de SeedRandom renverra une fonction de générateur aléatoire. SeedRandom peut être appelé sans arguments dans l'ordre. pour ensemencer la fonction aléatoire renvoyée avec l’heure actuelle, ou il est possible de l’appeler avec 1 ou 2 inters non non négatives en tant qu’arguments afin de l’ensemencer avec ces entiers. générateur à initier dans l’un des 2 ^ 53 états différents.

La fonction de générateur aléatoire renvoyée prend 1 argument entier nommé limit, la limite doit être comprise entre 1 et 4294965886, la fonction renvoie un nombre compris entre 0 et limit-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
}

Exemple d'utilisation:

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.

Ce générateur présente les propriétés suivantes:

  • Il a environ 2 ^ 64 différents états intérieurs possibles.
  • Il a une période d’environ 2 ^ 63 ans, bien plus que quiconque n’aura jamais réellement besoin d’un programme JavaScript.
  • Étant donné que les valeurs mod sont des nombres premiers, il n’ya pas de modèle simple dans la sortie, quelle que soit la limite choisie. Cela diffère de certains PRNG plus simples qui présentent des modèles assez systématiques.
  • Il supprime certains résultats afin d’obtenir une distribution parfaite quelle que soit la limite.
  • Il est relativement lent, tourne environ 10 000 000 fois par seconde sur ma machine.
7
aaaaaaaaaaaa

Si vous programmez en TypeScript, j'ai adapté l'implémentation de Mersenne Twister, intégrée dans la réponse de Christoph Henkelmann à ce fil, sous la forme d'une classe TypeScript:

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

vous pouvez ensuite l'utiliser comme suit:

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]

vérifiez la source pour plus de méthodes.

4
bennyl

Remarque: Ce code a été initialement inclus dans la question ci-dessus. Afin de garder la question courte et ciblée, je l’ai déplacée vers cette réponse du wiki de la communauté.

J'ai trouvé ce code très utile et il semble bien fonctionner pour obtenir un nombre aléatoire, puis utiliser la valeur d'origine, mais je ne sais pas trop comment fonctionne la logique (par exemple, d'où proviennent les numéros 2345678901, 48271 & 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.
*/
0
Ilmari Karonen