web-dev-qa-db-fra.com

Semer le générateur de nombres aléatoires en Javascript

Est-il possible d'ensemencer le générateur de nombres aléatoires (Math.random) en Javascript?

326
weepy

Non, ce n'est pas le cas, mais il est assez facile d'écrire votre propre générateur ou, mieux encore, d'utiliser un générateur existant. Départ: cette question connexe .

Voir aussi le blog de David Bau pour plus d'informations sur l'ensemencement .

168
PeterAllenWebb

NOTE: Malgré (ou plutôt à cause de) la brièveté et l'élégance apparente, cet algorithme n'est en aucun cas un algorithme de haute qualité en termes de hasard. Rechercher par exemple ceux énumérés dans cette réponse pour de meilleurs résultats.

(Adapté à l'origine d'une idée intelligente présentée dans un commentaire à une autre réponse.)

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

Vous pouvez définir seed sur n'importe quel nombre, évitez simplement zéro (ou un multiple de Math.PI).

À mon avis, l’élégance de cette solution vient de l’absence de nombres "magiques" (à part 10000, ce qui représente à peu près le nombre minimum de chiffres à jeter pour éviter les motifs bizarres - voir les résultats avec des valeurs 1 , 1 , 10 ). La brièveté, c'est aussi Nice.

C'est un peu plus lent que Math.random () (par un facteur 2 ou 3), mais je pense que c'est à peu près aussi rapide que toute autre solution écrite en JavaScript.

145
Antti Kissaniemi

J'ai implémenté un certain nombre de bonnes, courtes et rapides fonctions de copie-copier PRNG en JavaScript. Tous peuvent être ensemencés et fournir des chiffres de bonne qualité.

Commencez par initialiser correctement vos PRNG. La plupart des générateurs ci-dessous n'ont pas de procédure de génération de graine intégrée, mais en acceptent une ou plusieurs. valeurs-bits en tant que --- état ​​initial du PRNG. Des semences similaires (par exemple, une graine de 1 et 2) peuvent provoquer des corrélations dans des PRNG plus faibles, ce qui donne à la sortie des propriétés similaires (telles que des niveaux générés aléatoirement similaires). Pour éviter cela, il est préférable d'initialiser les PRNG avec une graine bien distribuée.

Heureusement, les fonctions de hachage sont très efficaces pour générer des graines pour les PRNG à partir de chaînes courtes. Une bonne fonction de hachage générera des résultats très différents, même lorsque deux chaînes sont similaires. Voici un exemple basé sur la fonction de mixage de MurmurHash3:

function xmur3(str) {
    for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
        h = Math.imul(h ^ str.charCodeAt(i), 3432918353),
        h = h << 13 | h >>> 19;
    return function() {
        h = Math.imul(h ^ h >>> 16, 2246822507);
        h = Math.imul(h ^ h >>> 13, 3266489909);
        return (h ^= h >>> 16) >>> 0;
    }
}

Chaque appel suivant à la fonction de retour produit une nouvelle valeur de hachage "aléatoire" sur 32 bits à utiliser comme germe dans un PRNG. Voici comment vous pourriez l'utiliser:

// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var Rand = sfc32(seed(), seed(), seed(), seed());

// Output one 32-bit hash to provide the seed for mulberry32.
var Rand = mulberry32(seed());

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

C'est bien sûr JS fonctionnel, mais cela pourrait être objectivé.

Une autre chose à noter est que ce sont tous des générateurs 32 bits, ce qui signifie que tout est lié par des opérations 32 bits, simulant du code C 32 bits. Cela s'avère être un bon compromis, car les entiers 32 bits obtiennent un peu d'optimisation dans les moteurs JS modernes. JS ne peut effectuer que des opérations sur les bits 32 bits et ne peut même pas effectuer de calcul 64 bits de toute façon. JS a une limite de nombres entiers de 53 bits, mais même dans ce cas, vous avez besoin de beaucoup de ruse pour les utiliser efficacement. C'est pourquoi le soi-disant super-rapide 53 bits de Baagøe générateur d'Alea se termine plus lentement ​​que ces implémentations.

En avant aux marchandises (les générateurs).


sfc32

Ce joyau provient de la suite de tests de nombres aléatoires PractRand, qu’il transmet sans problème. PractRand est censé être encore plus strict que TestU01. sfc32 a un état de 128 bits et est également très rapide en JS (xoshiro128 ** est légèrement plus rapide, mais de qualité inférieure). C'est probablement mon 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 indique qu'il passe tous les tests de gjrand ). Je recommanderais ceci 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 232. Idéal si vous ne voulez utiliser qu'un seul nombre entier sur 32 bits et ne vous souciez pas de problème d'anniversaire . Il y a 4,3 milliards d'états possibles dans Mulberry32 par rapport aux 340 undecillion contenus dans 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;
    }
}

xoshiro128 **

En date de mai 2018, xoshiro128 ** est le nouveau membre de la famille Xorshift . Il offre un état 128 bits et est très 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 en date de Blackman/Vigna, qui a également rédigé les PRNGs xorshift128 + et xoroshiro utilisés dans Google Chrome en 2015. Il est remarquable en tant que l’un des rares PRNG modernes avec une version 32 bits. xoroshiro64 ** est également une option prometteuse, mais n’a que l’état 64 bits et a été largement remplacé par xoshiro.

Les auteurs affirment qu'il passe bien les tests aléatoires ( avec toutefois des réserves ). D'autres chercheurs ont souligné l'échec de certains tests dans BigCrush (notamment LinearComp et BinaryRank). Mais dans la pratique, cela ne devrait pas avoir d’importance, surtout si la valeur 32 bits est convertie en un float compris entre 0 et 1 comme le sont ces PRNG. Toutefois, cela peut poser problème si vous utilisez les bits de poids faible.

JSF

C'est JSF ou 'smallprng' de Bob Jenkins (2007), le gars qui a fait ISAAC et SpookyHash . Il fonctionne bien sur les tests PractRand et devrait être assez rapide. La durée moyenne de la période est supposée être de 2 ^ 126 mais "n'a pas été formellement déterminée".

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) / 4294967296; // 2^32
    }
    seed >>>= 0;
    var s = [0xf1ea5eed, seed, seed, seed];
    for(var i=0;i<20;i++) jsf();
    return jsf;
}

Cette version n'a pas besoin d'une fonction de graine séparée. Mais en conséquence, seuls les 32 bits peuvent être amorcés et cette version pré-exécute jsf () 20 fois pour disperser l’état initial, ce qui peut être coûteux.

Si nécessaire, l’ensemble de l’état 128 bits peut être initialisé directement et la boucle for supprimée. J'ai décidé de conserver la construction d'origine car l'auteur avait vérifié la durée du cycle de chaque graine possible de 32 bits dans la configuration donnée.

LCG (aka Lehmer/Park-Miller RNG ou MLCG)

Celle-ci n’est là que pour fournir une meilleure alternative aux options mentionnées dans d’autres réponses, telles que les méthodes Math.sin ou Math.PI, qui sont moins cohérentes ou moins fiables d’une plate-forme à l’autre. Cette implémentation de LCG est extrêmement rapide mais ne possède qu'un état à 31 bits et échoue à certains tests statistiques que les générateurs précédemment mentionnés réussissent avec brio. C'est un one-liner cependant — ce qui est bien :).

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

Il s'agit du norme minimale RNG proposé par Park – Miller en 1988 et 199 et mis en œuvre en C++ 11 sous la forme minstd_Rand. N'oubliez pas que l'état et la période ne sont que de 31 bits (31 bits donnent 2 milliards d'états possibles, 32 bits donnent le double). C'est le type même de PRNG que d'autres essaient de remplacer.

Cela fonctionnera, mais je ne l'emploierai pas sauf si vous vraiment ​​avez besoin de rapidité et ne vous souciez pas de la qualité aléatoire (qu'est-ce qui est aléatoire de toute façon?) Ou si cela ne vous dérange pas du 31 bits état/taille de la période. Idéal pour un jeu de confiture ou une démo ou quelque chose. En outre, les GCL souffrent de corrélations entre semences, il est donc préférable d’écarter le premier résultat d’un GCS.

Il semble que d'autres multiplicateurs offrent un état 32 bits complet. Je ne sais pas si ces statistiques sont meilleures/pires sur le plan statistique que celles de Park-Miller, mais ici, elles sont complètes.

var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32;
var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32;

Ces multiplicateurs sont de: P. L'Ecuyer: Un tableau de générateurs linéaires congruentiels de différentes tailles et bonne structure de réseau, 30 avril 1997.

78
bryc

Non, mais voici un générateur pseudo-aléatoire simple, une implémentation de Multiply-with-carry Je suis adapté de Wikipedia (a été supprimé depuis):

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

// Takes any integer
function seed(i) {
    m_w = (123456789 + i) & mask;
    m_z = (987654321 - i) & mask;
}

// 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 & 65535)) >>> 0;
    result /= 4294967296;
    return result;
}

EDIT: fonction semence fixe en la réinitialisant m_z
EDIT2: De graves problèmes de mise en œuvre ont été corrigés

38
Antti Kissaniemi

L'algorithme d'Antti Sykäri est joli et court. J'ai initialement fait une variante qui a remplacé Math.random en Javascript lorsque vous appelez Math.seed (s), mais Jason a ensuite fait remarquer que renvoyer 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 ne possède pas: plusieurs générateurs aléatoires indépendants. Cela est particulièrement important si vous souhaitez exécuter plusieurs simulations répétables en même temps.

25
Remco Kranenburg

Voir les travaux de Pierre L'Ecuyer à la fin des années 80 et au début des années 90. Il y a d'autres aussi. Créer vous-même un générateur de nombres (pseudo) aléatoires, si vous n'êtes pas un expert, est assez dangereux, car il est très probable que les résultats ne soient pas statistiquement aléatoires ou qu'ils aient une courte période. Pierre (et d’autres) ont mis en place de bons (pseudo) générateurs de nombres aléatoires faciles à mettre en oeuvre. J'utilise l'un de ses générateurs LFSR.

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

Phil Troy

11
user2383235

En combinant certaines des réponses précédentes, voici la fonction aléatoire que vous recherchez:

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

    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 & 65535)) >>> 0;
      result /= 4294967296;
      return result;
    }
}

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

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

La suggestion de Dave Scotese est utile mais, comme l'ont souligné d'autres personnes, elle n'est pas assez uniformément répartie.

Cependant, ce n'est pas à cause des arguments entiers de sin. C'est simplement à cause de l'étendue du péché, qui se trouve être une projection unidimensionnelle d'un cercle. Si vous preniez 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élangez-le un peu avec xor. Le facteur réel n'a pas beaucoup d'importance non plus.

Pour générer n nombres pseudo 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))

Notez également que vous ne pouvez pas utiliser de séquences pseudo aléatoires lorsqu'une entropie réelle est nécessaire.

3
Lajos Bodrogi

Beaucoup de gens qui ont besoin d'un générateur de nombres aléatoires à ensemencer en Javascript utilisent ces jours-ci le module seedrandom de David Ba .

3
Martin Omander

Math.random non, mais la bibliothèque a exécuté résout ce problème. Il a presque toutes les distributions que vous pouvez imaginer et supporte la génération de nombres aléatoires. Exemple:

ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
0
Ulf Aslak