Générateur de nombres aléatoires JavaScript prédéfinis

150

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

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 le faire produire une séquence répétable de (pseudo) nombres aléatoires?

scunliffe
la source
1
Remarque: Afin de garder cette question courte et ciblée, j'ai divisé le code qui était dans la question ci-dessus en une réponse du wiki communautaire ci-dessous.
Ilmari Karonen
1
Voir aussi stackoverflow.com/questions/521295
Palimondo

Réponses:

124

Une option est http://davidbau.com/seedrandom qui est un remplacement instantané Math.random () basé sur RC4 avec de belles propriétés.

David Bau
la source
18
Le seedrandom de David Bau est depuis devenu assez populaire pour qu'il le conserve ici sur github . C'est dommage qu'ECMAScript ait été si longtemps en arrière-plan que des choses comme celle-ci ne soient pas incluses dans le langage. Sérieusement, pas de semis !!!
Mangez chez Joes le
2
@EatatJoes, c'est à la fois la honte et la gloire de JS que cela soit à la fois nécessaire et possible. C'est plutôt cool que vous puissiez inclure un fichier et obtenir des modifications rétrocompatibles apportées à l'objet Math. Pas mal pour 10 jours de travail, Brendan Eich.
Bruno Bronosky
2
Pour tous ceux qui recherchent la page npm pour ce projet: npmjs.com/package/seedrandom
Kip le
27

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

Je ne sais pas quel RNG vous utilisez, mais il est préférable de le connaître et de le documenter afin que vous soyez conscient de ses caractéristiques et de ses limites.

Comme l'a dit Starkii, Mersenne Twister est un bon PRNG, mais ce 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 certaines des constantes populaires.

EDIT: considérez les excellentes options de cette réponse pour les implémentations RNG courtes, y compris une option 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));

orip
la source
2
Le module ne devrait-il pas être 2 ^ 31? J'ai lu cet algorithme sur wiki .
Trantor Liu
3
Juste pour que vous soyez conscient, ce n'est pas «correct» dans le sens où cela ne produit pas ce que les mathématiques dictent. En d'autres termes, une langue capable de gérer ces grands nombres aura un résultat différent. JS s'étouffe sur les grands nombres et hache la précision (ce sont des flotteurs, après tout).
DDS
4
-1 Cette implémentation LCG dépasse la limite pour les entiers exacts dans JavaScript, car cela this.a * this.stateentraînera probablement un nombre supérieur à 2 ^ 53. Le résultat est une plage de production limitée, et pour certaines semences peut-être une période très courte. De plus, en général, l'utilisation d'une puissance de deux pour maboutir à des modèles assez évidents, lorsque vous dépensez une opération de module plutôt qu'une simple troncature de toute façon, il n'y a aucune raison de ne pas utiliser de prime.
aaaaaaaaaaaa
22

Si vous souhaitez pouvoir spécifier la valeur de départ, il vous suffit de remplacer les appels à getSeconds()et getMinutes(). Vous pouvez passer un int et utiliser la moitié du mod 60 pour la valeur des secondes et l'autre moitié du 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 nombres aléatoires, il suffit d'essayer 3600 combinaisons de secondes et de minutes différentes. Cela signifie également qu'il n'y a que 3600 graines différentes possibles. Cela peut être corrigé, mais je me méfierais de ce RNG depuis le début.

Si vous souhaitez 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 avoir raison et faire référence à cela 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

Starkii
la source
1
Un lien vers les implémentations JS de Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip
1
@orip Avez-vous une source pour les 3600 états initiaux? Mersenne Twister est ensemencé par un nombre de 32 bits, donc le PRNG devrait avoir 4 milliards d'états initiaux - seulement si la graine initiale est vraiment aléatoire.
Tobias P.
2
@TobiasP. Je faisais référence à la suggestion d'amorcer avec une combinaison de getSeconds () et getMinutes (), 60 * 60 == 3600 états initiaux possibles. Je ne parlais pas de Mersenne Twister.
orip
3
@orip Ok, n'était pas clair. Vous parliez de Mersenne Twister et dans la phrase suivante sur les états initiaux;)
Tobias P.
2
Le demandeur ne fait aucune mention qu'il a besoin d'une "bonne" génération de nombres aléatoires pour tout type d'application cryptographiquement sensible. Bien que toute la réponse soit vraie, seul le premier paragraphe est réellement pertinent par rapport à la question posée. Ajoutez peut-être un extrait de code de la solution suggérée.
V.Rubinetti
12

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

Christoph Henkelmann
la source
8

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

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

mipadi
la source
7

Ce qui suit est un PRNG qui peut être alimenté avec une graine personnalisée. L'appel SeedRandomrenverra une fonction de générateur aléatoire. SeedRandompeut être appelée sans argument afin d'amorcer la fonction aléatoire retournée avec l'heure actuelle, ou elle peut être appelée avec 1 ou 2 inters non négatifs comme arguments afin de l'amorcer avec ces entiers. En raison de la précision du point flottant, un ensemencement avec une seule valeur ne permettra au générateur de démarrer que 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 retournera 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 états internes possibles.
  • Il a une période d'environ 2 ^ 63, beaucoup plus que quiconque n'aura jamais besoin de façon réaliste dans un programme JavaScript.
  • Étant donné que les modvaleurs sont 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.
  • Il est relativement lent, tourne environ 10 000 000 fois par seconde sur ma machine.
aaaaaaaaaaaa
la source
2
Pourquoi cela produit-il un modèle? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski
@TimothyKanski Parce que vous l'utilisez mal. Je ne suis pas un expert, mais cela se produit parce que vous initialisez le générateur à chaque itération, ne voyant que sa première valeur en fonction de la graine, et PAS d'itération sur les numéros suivants du générateur. Je crois que cela se produira dans tout PRNG qui ne hache pas la graine dans l'intervalle spécifié.
bryc
5

Si vous programmez en Typescript, j'ai adapté l'implémentation Mersenne Twister qui a été apportée dans la réponse de Christoph Henkelmann à ce fil en tant que classe dactylographiée:

/**
 * 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.

Bennyl
la source
0

Remarque: ce code était 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 communautaire.

J'ai trouvé ce code en train de tourner 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 du fonctionnement de la logique (par exemple, d'où viennent 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.
*/
Ilmari Karonen
la source
3
Wow, les fonctions RandomNumberGeneratoret nextRandomNumberdatent en fait de 1996. Il est censé être un Lehmer / LCG RNG. Il utilise des mathématiques intelligentes pour effectuer de l'arithmétique modulo sur des entiers 32 bits qui seraient autrement trop petits pour contenir des valeurs intermédiaires. Le fait est que JavaScript n'implémente pas les entiers 32 bits, mais plutôt les flottants 64 bits, et puisque la division n'est pas une division entière comme ce code suppose que le résultat n'est pas un générateur Lehmer. Cela produit des résultats qui semblent aléatoires, mais les garanties d'un générateur Lehmer ne s'appliquent pas.
aaaaaaaaaaaa
1
La createRandomNumberfonction est un ajout ultérieur, elle fait à peu près tout de travers, notamment elle instancie un nouveau RNG chaque fois qu'elle est appelée, ce qui signifie que les appels en succession rapide utiliseront tous le même float. Dans le code donné, il est presque impossible 'a'd'être associé à autre chose que '1'et 'red'.
aaaaaaaaaaaa
-2

OK, voici la solution sur laquelle j'ai choisi.

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

Le point 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 flottant, la valeur serait écrasée chaque fois que la fonction était appelée, car les valeurs des entiers, des flottants, des chaînes, etc. sont stockées directement dans la pile par rapport aux pointeurs comme dans le cas des tableaux et d'autres objets. Ainsi, il est possible que la valeur de la graine reste persistante.

Enfin, il est possible de définir la fonction "srandom ()" de telle sorte que ce soit une méthode de l'objet "Math", mais je vous laisse le soin de 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)
posfan12
la source
PS - Je ne connais pas encore très bien Stack Overflow, mais pourquoi les publications ne sont-elles pas dans l'ordre chronologique?
posfan12
Salut @ posfan12 - les réponses aux questions sont généralement classées par "upvotes" de telle sorte que "la crème monte vers le haut". Cependant, pour assurer une visualisation équitable des réponses avec le même score, elles sont affichées dans un ordre aléatoire. Comme c'était ma question à l'origine ;-) Je ne manquerai certainement pas de la vérifier sous peu. Si je (ou d'autres) trouve cette réponse utile, nous la voterons, et si je trouve que c'est la réponse «correcte», vous verrez également une coche verte ajoutée à cette réponse. - Bienvenue dans StackOverflow!
scunliffe
2
-1 Cette implémentation LCG dépasse la limite des nombres entiers exacts dans JavaScript, car cela seedobj[0] * seedobjaentraînera probablement un nombre supérieur à 2 ^ 53. Le résultat est une plage de production limitée, et pour certaines semences peut-être une période très courte.
aaaaaaaaaaaa