Ensemencement du générateur de nombres aléatoires en Javascript

est-il possible de semer le générateur de nombres aléatoires (Math.au hasard) en Javascript?

268
demandé sur jww 2009-02-06 20:37:39

12 réponses

Non, il n'est pas, mais il est assez facile d'écrire votre propre générateur, ou mieux encore, utiliser une existante. Découvrez: cette question relative à la .

Aussi, voir David Bau blog de plus d'informations sur l'ensemencement .

152
répondu PeterAllenWebb 2017-05-23 12:02:45

mon autre réponse représente un algorithme plus traditionnel, mais J'ai trouvé le commentaire de Dave Scotese à cette réponse plus éloquent. Malheureusement, c'est assez lent à cause de la manipulation des cordes.

Voici une version qui est environ 20 fois plus rapide et un peu plus précis.

var seed = 1;
function random() {
    var x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}

vous pouvez définir seed pour être n'importe quel nombre, juste éviter zéro (ou n'importe quel multiple de Math.PI).

l'élégance de cette solution, à mon avis, vient de l'absence de n'importe quels nombres "magiques" (outre 10000, qui représente environ le nombre minimum de chiffres que vous devez jeter pour éviter des modèles impairs - voir les résultats avec des valeurs 10 , 100 , 1000 ). La brièveté est aussi agréable.

c'est un peu plus lent que les maths.random() (par un facteur de 2 ou 3), mais je crois que c'est à peu près aussi rapide que tout autre solution écrite en JavaScript.

116
répondu Antti Sykäri 2017-05-23 12:02:45

Non, Mais voici un simple générateur de pseudorandom que j'ai adapté de Wikipedia :

var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;

// Takes any integer
function seed(i) {
    m_w = i;
    m_z = 987654321;
}

// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
    m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
    m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
    var result = ((m_z << 16) + m_w) & mask;
    result /= 4294967296;
    return result + 0.5;
}

modifier: fixe fonction de graine en le faisant réinitialiser m_z

33
répondu Antti Sykäri 2017-07-05 16:23:29

l'algorithme D'Antti Sykäri est court et agréable. J'ai d'abord fait une variation qui a remplacé les maths de Javascript.aléatoire quand vous appelez les maths.seed (s), mais ensuite Jason a commenté que le retour de la fonction serait mieux:

Math.seed = function(s) {
    return function() {
        s = Math.sin(s) * 10000; return s - Math.floor(s);
    };
};

// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());

cela vous donne une autre fonctionnalité que Javascript n'a pas: plusieurs générateurs aléatoires indépendants. Cela est particulièrement important si vous voulez avoir plusieurs simulations reproductibles fonctionnant en même temps.

23
répondu Remco Kranenburg 2014-06-19 12:58:26

j'ai implémenté un certain nombre de bonnes, courtes, rapides fonctions de copie-pastable PRNG. Tous d'entre eux peuvent être ensemencés.

n'oubliez pas de bien ensemencer votre PRNGs. plusieurs des génératrices ci-dessous n'ont pas de semoir intégré. Vous pouvez utiliser un algorithme de hachage pour générer une graine d'un entier ou une chaîne. Voici un exemple basé sur FNV1a-Mulvey hash:

function xfnv1a(k) {
    for(var i = 0, h = 2166136261 >>> 0; i < k.length; i++)
        h = Math.imul(h ^ k.charCodeAt(i), 16777619);
    return function() {
        h += h << 13; h ^= h >>> 7;
        h += h << 3;  h ^= h >>> 17;
        return (h += h << 5) >>> 0;
    }
}

xfnv1a transforme le de Bret Mulvey modifié FNV1a dans ce qui est essentiellement un PRNG. En tant que PRNG il n'est probablement pas très bon, mais ce qu'il est bon pour est générant un pool d'entropie pour un autre PRNG. Depuis qu'il Hache une chaîne, même un seul bit retourné produira des résultats très différents. La plupart des Gprn ont besoin d'entropie suffisante dans l'état initial pour fournir des résultats décents. Certaines implémentations de pré-exécution d'un GÉNÉRATEUR jusqu'à ce qu'il produise des résultats acceptables, soucieuse de la nécessité d'une distribution initiale état.

Voici comment vous l'utiliseriez:

// Create a xfnv1a state:
var seed = xfnv1a("apples");
// Output four 32-bit hashes to produce the seed for sfc32.
var rand = sfc32(seed(), seed(), seed(), seed());

// Or: output one 32-bit hash to produce the seed for mulberry32.
var rand = mulberry32(seed());

// Obtain sequential random numbers like so:
rand();
rand();

vers les marchandises.


sfc32

cette gemme vient de la suite de test de nombres aléatoires de PractRand, dont il passe sans issue. PractRand est censément encore plus sévère que TestU01. Il est également très rapide en JS (xoshiro128* * est légèrement plus rapide, mais de moins bonne qualité). C'est probablement ma PRNG de choix.

function sfc32(a, b, c, d) {
    return function() {
      a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; 
      var t = (a + b) | 0;
      a = b ^ b >>> 9;
      b = c + (c << 3) | 0;
      c = (c << 21 | c >>> 11);
      d = d + 1 | 0;
      t = t + d | 0;
      c = c + t | 0;
      return (t >>> 0) / 4294967296;
    }
}

Mulberry32

Mulberry32 est également assez rapide et de bonne qualité (l'auteur déclare qu'il passe tous les tests de gjrand). Je le recommande si vous avez juste besoin d'un simple mais décent PRNG.

il a un État de 32 bits et une période complète de 2 32 . Idéal si vous voulez seulement semer avec un entier 32 bits et ne vous souciez pas du problème d'anniversaire . Il y a 4,3 milliards d'États contre 340 millions de sfc32/xoshiro128**.

function mulberry32(a) {
    return function() {
      var t = a += 0x6D2B79F5;
      t = Math.imul(t ^ t >>> 15, t | 1);
      t ^= t + Math.imul(t ^ t >>> 7, t | 61);
      return ((t ^ t >>> 14) >>> 0) / 4294967296;
    }
}

Alea

Alea est bon pour la rapidité, de haute qualité aléatoire (conçu spécifiquement pour JS):

function Alea(seed) {
    if(seed === undefined) {seed = +new Date() + Math.random();}
    function Mash() {
        var n = 4022871197;
        return function(r) {
            for(var t, s, u = 0, e = 0.02519603282416938; u < r.length; u++)
            s = r.charCodeAt(u), f = (e * (n += s) - (n*e|0)),
            n = 4294967296 * ((t = f * (e*n|0)) - (t|0)) + (t|0);
            return (n|0) * 2.3283064365386963e-10;
        }
    }
    return function() {
        var m = Mash(), a = m(" "), b = m(" "), c = m(" "), x = 1, y;
        seed = seed.toString(), a -= m(seed), b -= m(seed), c -= m(seed);
        a < 0 && a++, b < 0 && b++, c < 0 && c++;
        return function() {
            var y = x * 2.3283064365386963e-10 + a * 2091639; a = b, b = c;
            return c = y - (x = y|0);
        };
    }();
}

celui-ci a semé intégré en utilisant la fonction Mash . Il ne comprend pas la méthode fract52 de l'original. Si c'est important, c'est simplement une nouvelle transformation de la sortie .

sa vitesse est assez bonne et semble avoir un état de 96 bits (mais je ne suis pas sûr). L'auteur (Baagøe, 2010) déclare "la période est proche de 2^116, Il passe BigCrush, et c'est le javascript le plus rapide PRNG je sais qui le fait". Je ne peux pas vérifier qu'il passe en effet BigCrush, mais j'ai vu résultats de Dieharder qui semblent prometteurs. Il est inclus dans le seedrandom de la bibliothèque.

mise à jour: la vitesse de ce PRNG est plutôt lente par rapport à mes autres implémentations. Il ne ralentira probablement pas votre jeu / application dans la pratique, mais si la performance absolue est un problème, considérez l'un des autres.

pour l'utiliser, appeler la fonction principale pour initialiser le PRNG avec une graine, puis appeler sa fonction retournée pour générer des numéros suivants:

var rand = Alea("123");
rand(); // 0.4801303152926266

xoshiro128* *

depuis mai 2018, xoshiro128** est le nouveau membre de la famille xorshift. Il dispose de 128 bits de l'état, et est super rapide.

function xoshiro128ss(a, b, c, d) {
    return function() {
        var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
        c ^= a; d ^= b;
        b ^= c; a ^= d; c ^= t;
        d = d << 11 | d >>> 21;
        return (r >>> 0) / 4294967296;
    }
}

ce PRNG est le dernier par Blackman/Vigna qui a également fait xorshift128+ et xoroshiro utilisés dans Google Chrome. Il est remarquable comme L'un des rares Rpng 32 bits modernes. xoroshiro64** est également prometteuse, mais ne dispose que d'une version 64 bits de l'état et a été largement remplacé par xoshiro.

utiliser comme suit:

var rand = xoshiro128ss( initseed([1, 2, 3, 4]) );
rand(); // 0.7410467516165227

les auteurs affirment qu'il réussit bien les tests d'aléatoire ( , mais avec des mises en garde ). D'autres chercheurs ont fait remarquer que certains essais de BigCrush (en particulier LinearComp et BinaryRank) ont échoué. Mais cela ne devrait pas avoir d'importance dans la pratique, surtout si la valeur de 32 bits est convertie en un flottant entre 0-1 comme ces PRNGs le sont. Cependant, si vous vous fiez sur les bits bas d'une manière maladroite, il peut causer un question.

JSF

C'est JSF ou smallprng par Bob Jenkins (2007), le gars qui a fait ISAAC et SpookyHash .

function JSF(seed) {
    function jsf() {
        var e = s[0] - (s[1]<<27 | s[1]>>5);
         s[0] = s[1] ^ (s[2]<<17 | s[2]>>15),
         s[1] = s[2] + s[3],
         s[2] = s[3] + e, s[3] = s[0] + e;
        return (s[3] >>> 0) / 4294967295; // 2^32-1
    }
    seed >>>= 0;
    var s = [0xf1ea5eed, seed, seed, seed];
    for(var i=0;i<20;i++) jsf();
    return jsf;
}

il devrait être assez rapide, mais il fonctionne jsf() 20 fois d'abord à la graine qui coûte un peu. Apparemment ,il est aussi fonctionne bien sur les tests de PractRand. Il a 128 bits d'état, cependant seulement 32 bits est semable (éventuellement choisi d'éviter qu'une fonction de hachage). La durée moyenne de la période est supposée être de 2 ^ 126 mais n'a pas été formellement déterminée.

Utilisé comme suit:

var rand = JSF(123);
rand(); // 0.098275076597929

Lehmer LCG

celui-ci est seulement ici pour illustrer comment mauvais certains des RNGs dans d'autres réponses ici sont, comme le Math.sin ou Math.PI méthodes. C'est extrêmement rapide mais n'a qu'un état 31 bits et échoue les tests statistiques.

var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;

c'est un one-liner cependant qui est agréable :). Il s'agit du norme minimale RNG tel que proposé par Park–Miller en 1988 & 1993 et mis en œuvre en C++11 comme minstd_rand . Gardez à l'esprit que l'état et la période sont seulement 31 bits. C'est le type de GPRR que d'autres essaient de remplacer.

ça va marcher, mais je ne l'utiliserai que si vous vraiment ont besoin de performance et ne se soucient pas de la qualité de l'aléatoire (qu'est-ce que l'aléatoire de toute façon?) ou la taille d'état/période de 31 bits. Idéal pour un jeu jam ou une démo ou quelque chose.

utiliser comme suit:

var rand = LCG(123);
rand(); // 0.45899124443531036

il semble qu'il y ait d'autres LCG qui vous donnent le plein 32 bits. Je ne sais pas si les graines doivent être étranges, ou si elles sont meilleures ou pires que celle de Park-Miller.

var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32;
var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32;
15
répondu bryc 2018-08-31 05:58:31

voir les travaux de Pierre L'Ecuyer remontant à la fin des années 1980 et au début des années 1990. Il y en a d'autres aussi. Créer un (pseudo) générateur de nombres aléatoires sur votre propre, si vous n'êtes pas un expert, est assez dangereux, parce qu'il ya une forte probabilité que les résultats ne sont pas statistiquement aléatoire ou d'avoir une petite période. Pierre (et d'autres) ont mis en place quelques bons (pseudo) générateurs de nombres aléatoires qui sont faciles à mettre en œuvre. J'utilise l'un de ses LFSR générateurs.

https://www.iro.umontreal.ca / ~lecuyer / myftp / papers / handstat.pdf

Phil Troy

10
répondu user2383235 2017-03-04 18:56:59

en combinant quelques-unes des réponses précédentes, voici la fonction aléatoire semable que vous recherchez:

Math.seed = function(s) {
    var m_w  = s;
    var m_z  = 987654321;
    var mask = 0xffffffff;

    return function() {
      m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
      m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;

      var result = ((m_z << 16) + m_w) & mask;
      result /= 4294967296;

      return result + 0.5;
    }
}

var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();

soyez prudent en utilisant celui-ci cependant, je ne crois pas que la distribution des nombres aléatoires est très bonne, il semble peser vers le 0 à .5 gamme. Au moins c'était mon expérience dans la marche aléatoire de visualisation que je faisais.

4
répondu user3158327 2015-04-04 19:32:14

pour écrire votre propre pseudo générateur aléatoire est assez simple.

la suggestion de Dave Scotese est utile mais, comme d'autres l'ont souligné, elle n'est pas distribuée de façon tout à fait uniforme.

cependant, ce n'est pas à cause des arguments integer du péché. C'est simplement à cause de la portée du péché, qui se trouve être une projection unidimensionnelle d'un cercle. Si vous voulez prendre l'angle du cercle à la place il serait uniforme.

Ainsi, au lieu de sin(x), utilisez arg(exp(i * x)) / (2 * PI).

Si vous n'aimez pas l'ordre linéaire, mélanger un peu avec xor. Le facteur réel n'a pas beaucoup d'importance non plus.

pour générer n pseudo nombres aléatoires on pourrait utiliser le code:

function psora(k, n) {
  var r = Math.PI * (k ^ n)
  return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))

veuillez également noter que vous ne pouvez pas utiliser des séquences pseudo-aléatoires lorsque l'entropie réelle est nécessaire.

3
répondu Lajos Bodrogi 2017-02-26 00:33:09

beaucoup de gens qui ont besoin d'un générateur de nombres aléatoires seedable en Javascript ces jours-ci utilisent le module Seedrandom de David Bau .

2
répondu Martin Omander 2017-12-07 15:52:34

j'ai écrit une fonction qui renvoie un nombre aléatoire ensemencé, elle utilise les mathématiques.le péché d'avoir un long nombre aléatoire et utilise la graine de choisir les numéros à partir de cela.

utiliser:

seedRandom ("k9]:2@", 15)

il vous rendra votre numéro de semis le premier paramètre est n'importe quelle valeur de chaîne ; votre graine. le deuxième paramètre est le nombre de chiffres sera de retour.

     function seedRandom(inputSeed, lengthOfNumber){

           var output = "";
           var seed = inputSeed.toString();
           var newSeed = 0;
           var characterArray = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','U','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')',' ','[','{',']','}','|',';',':',"'",',','<','.','>','/','?','`','~','-','_','=','+'];
           var longNum = "";
           var counter = 0;
           var accumulator = 0;

           for(var i = 0; i < seed.length; i++){
                var a = seed.length - (i+1);
                for(var x = 0; x < characterArray.length; x++){
                     var tempX = x.toString();
                     var lastDigit = tempX.charAt(tempX.length-1);
                     var xOutput = parseInt(lastDigit);
                     addToSeed(characterArray[x], xOutput, a, i); 
                }                  
           }

                function addToSeed(character, value, a, i){
                     if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
                }
                newSeed = newSeed.toString();

                var copy = newSeed;
           for(var i=0; i<lengthOfNumber*9; i++){
                newSeed = newSeed + copy;
                var x = Math.sin(20982+(i)) * 10000;
                var y = Math.floor((x - Math.floor(x))*10);
                longNum = longNum + y.toString()
           }

           for(var i=0; i<lengthOfNumber; i++){
                output = output + longNum.charAt(accumulator);
                counter++;
                accumulator = accumulator + parseInt(newSeed.charAt(counter));
           }
           return(output)
      }
-1
répondu Tyler Hudson 2018-04-13 15:45:44

Une approche simple pour un montant fixe de la graine:

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}
-3
répondu Carlos Oliveira 2018-04-30 02:24:18

pour un nombre entre 0 et 100.

Number.parseInt(Math.floor(Math.random() * 100))
-5
répondu Lord Elrond 2018-04-10 15:47:34