Générateur de nombres aléatoires JavaScript Seedable

Le JavaScript Math.random() la fonction renvoie une valeur aléatoire entre 0 et 1, automatiquement ensemencée en fonction de l'heure actuelle (similaire à Java je crois). Cependant, je ne pense pas qu'il y ait un moyen de vous donner 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 de pouvoir produire une séquence reproductible de nombres (pseudo)aléatoires?

121
demandé sur Ilmari Karonen 2009-01-08 16:51:24

9 réponses

Une option est http://davidbau.com/seedrandom qui est un calcul basé sur RC4 semable.random() baisse-dans le remplacement avec de belles propriétés.

97
répondu David Bau 2010-02-03 14:18:24

Si vous n'avez pas besoin de la capacité d'ensemencement, utilisez simplement Math.random() et construisez des fonctions d'assistance autour d'elle (par exemple. randRange(start, end)).

Je ne suis pas sûr du RNG que vous utilisez, mais il est préférable de le connaître et de le documenter pour que vous soyez conscient de ses caractéristiques et de 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 d'implémenter un LCG - c'est très facile, a des qualités aléatoires décentes (pas aussi bonnes que Mersenne Twister), et vous pouvez utiliser les constantes.

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));
21
répondu orip 2018-03-22 19:39:50

Si vous voulez pouvoir spécifier la graine, il vous suffit de remplacer les appels à getSeconds() et getMinutes(). Vous pouvez passer un int et en utiliser la moitié 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 à des ordures. Faire une bonne génération de nombres aléatoires 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 flux de Aléatoire numbers ne nécessite que d'essayer 3600 combinaisons différentes de secondes et de minutes. Cela signifie également qu'il n'y a que 3600 graines différentes possibles. C'est corrigeable, mais je me méfierais de ce RNG depuis 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 d'excellentes performances.

EDIT: je devrais vraiment être correct et me référer à ceci comme un générateur de nombres Pseudo-aléatoires ou PRNG.

" quiconque utilise des méthodes arithmétiques pour produire des nombres aléatoires est dans un État de péché."
                                                                                                                                                          --- John von Neumann

18
répondu Starkii 2010-11-07 13:55:18

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

10
répondu Christoph Henkelmann 2011-03-08 23:25:34

Le code que vous avez listé ressemble à un Lehmer RNG . Si c'est le cas, alors 2147483647 est le plus grand entier signé 32 bits, 2147483647 est le plus grand de 32 bits en premier, et 48271 est une période complète multiplicateur qui est utilisé pour générer les nombres.

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

8
répondu mipadi 2009-01-08 15:24:08

Ce qui suit est un PRNG qui peut être nourri d'une graine personnalisée. L'appel de SeedRandom renverra une fonction de générateur aléatoire. SeedRandom peut être appelé sans arguments afin d'ensemencer la fonction aléatoire retournée avec l'heure actuelle, ou il peut être appelé avec 1 ou 2 inters non négatifs comme arguments afin de l'ensemencer avec ces entiers. En raison de la précision du point flottant, l'ensemencement avec seulement 1 valeur ne permettra que d'initier le générateur à l'un des 2^53 États différents.

Le retour la fonction de générateur aléatoire prend 1 argument entier nommé limit, la limite doit être dans la plage 1 à 4294965886, la fonction retournera un nombre dans la plage 0 à 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 internes possibles.
  • Il a une période d'environ 2 ^ 63, beaucoup plus que quiconque aura besoin de manière réaliste dans un programme JavaScript.
  • en raison de les valeurs mod étant des nombres premiers, il n'y a pas de modèle simple dans la sortie, quelle que soit la limite choisie. Ceci est différent de certains PRNG plus simples qui présentent des modèles assez systématiques.
  • il rejette certains résultats afin d'obtenir une distribution parfaite quelle que soit la limite.
  • c'est relativement lent, tourne autour de 10 000 000 fois par seconde sur ma machine.
5
répondu aaaaaaaaaaaa 2014-03-10 23:39:29

Si vous programmez en Typescript, j'ai adapté L'implémentation de Mersenne Twister qui a été apportée dans la réponse de Christoph Henkelmann à ce thread en tant que 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 que 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
répondu bennyl 2015-09-12 10:49:16

Remarque: Ce code a été inclus dans la question ci-dessus. Dans l'intérêt de garder la question courte et ciblée, Je l'ai déplacée vers cette réponse Wiki de la communauté.

J'ai trouvé ce code et il semble fonctionner correctement pour obtenir un nombre aléatoire, puis utiliser la graine par la suite, mais je ne suis pas tout à fait sûr de comment fonctionne la logique (par exemple, d'où proviennent les numéros 2345678901, 48271 et 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.
*/
1
répondu Ilmari Karonen 2014-03-10 21:54:27

OK, voici la solution sur laquelle je me suis installé.

Vous créez D'abord une valeur seed en utilisant la fonction "newseed ()". Ensuite, vous passez la valeur de départ à la fonction" srandom ()". Enfin, la fonction" srandom () " renvoie une valeur pseudo aléatoire comprise entre 0 et 1.

Le bit crucial est que la valeur de départ est stockée dans un tableau. S'il s'agissait simplement d'un entier ou d'un flotteur, la valeur serait écrasée chaque fois que la fonction était appelée, puisque les valeurs des entiers, des flotteurs, des chaînes et ainsi de suite sont stockés directement dans la pile par rapport aux pointeurs comme dans le cas des tableaux et autres objets. Ainsi, il est possible que la valeur de la graine reste persistante.

Enfin, il est possible de définir la fonction " srandom () "telle que c'est une méthode de l'objet" Math", mais je vous laisse le soin de le comprendre. ;)

Bonne chance!

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 (Mon environnement cible personnel):

-- 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)
-2
répondu posfan12 2010-12-23 05:14:25