Ist es möglich, den Zufallszahlengenerator (Math.random) in Javascript zu setzen?
Ist es möglich, den Zufallszahlengenerator (Math.random) in Javascript zu setzen?
Antworten:
Nein, ist es nicht, aber es ist ziemlich einfach, einen eigenen Generator zu schreiben oder noch besser einen vorhandenen zu verwenden. Check out: diese verwandte Frage .
Weitere Informationen zum Seeding finden Sie auch im Blog von David Bau .
HINWEIS: Trotz (oder vielmehr wegen) Prägnanz und offensichtlicher Eleganz ist dieser Algorithmus in Bezug auf die Zufälligkeit keineswegs von hoher Qualität. Suchen Sie nach den in dieser Antwort aufgeführten, um bessere Ergebnisse zu erzielen.
(Ursprünglich angepasst von einer cleveren Idee, die in einem Kommentar zu einer anderen Antwort präsentiert wurde.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Sie können einstellen seed
eine beliebige Zahl festlegen. Vermeiden Sie einfach Null (oder ein Vielfaches von Math.PI).
Die Eleganz dieser Lösung beruht meiner Meinung nach auf dem Fehlen jeglicher "magischer" Zahlen (neben 10000, was ungefähr der Mindestanzahl von Ziffern entspricht, die Sie wegwerfen müssen, um ungerade Muster zu vermeiden - siehe Ergebnisse mit Werten 10 , 100 , 1000 ). Kürze ist auch schön.
Es ist etwas langsamer als Math.random () (um den Faktor 2 oder 3), aber ich glaube, es ist ungefähr so schnell wie jede andere in JavaScript geschriebene Lösung.
Ich habe eine Reihe guter, kurzer und schneller Funktionen des Pseudozufallszahlengenerators (PRNG) in einfachem JavaScript implementiert . Alle von ihnen können ausgesät werden und liefern gute Qualitätszahlen.
Achten Sie zunächst darauf, Ihre PRNGs ordnungsgemäß zu initialisieren. Die meisten der Generatoren haben unten keine integrierte Prozedur Samen Erzeugen (der Einfachheit halber), aber akzeptieren einen oder mehrere 32-Bit - Werte als Anfangszustand des PRNG. Ähnliche Keime (z. B. ein einfacher Keim von 1 und 2) können Korrelationen in schwächeren PRNGs verursachen, was dazu führt, dass die Ausgabe ähnliche Eigenschaften aufweist (z. B. zufällig erzeugte Niveaus sind ähnlich). Um dies zu vermeiden, empfiehlt es sich, PRNGs mit einem gut verteilten Seed zu initialisieren.
Glücklicherweise sind Hash-Funktionen sehr gut darin, aus kurzen Strings Seeds für PRNGs zu generieren. Eine gute Hash-Funktion führt zu sehr unterschiedlichen Ergebnissen, selbst wenn zwei Zeichenfolgen ähnlich sind. Hier ist ein Beispiel, das auf der Mischfunktion von MurmurHash3 basiert:
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;
}
}
Jeder nachfolgende Aufruf der Rückgabefunktion von xmur3
erzeugt einen neuen "zufälligen" 32-Bit-Hashwert, der als Startwert in einem PRNG verwendet wird. So können Sie es verwenden:
// 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();
Alternativ können Sie einfach einige Dummy-Daten auswählen, mit denen der Startwert aufgefüllt werden soll, und den Generator einige Male (12 bis 20 Iterationen) vorschieben, um den Anfangszustand gründlich zu mischen. Dies wird häufig in Referenzimplementierungen von PRNGs gesehen, begrenzt jedoch die Anzahl der Anfangszustände.
var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();
Die Ausgabe dieser PRNG-Funktionen erzeugt eine positive 32-Bit-Zahl (0 bis 2 32 -1), die dann in eine Gleitkommazahl zwischen 0-1 (0 einschließlich, 1 exklusiv) umgewandelt wird, was entspricht Math.random()
, wenn Sie Zufallszahlen wünschen Lesen Sie diesen Artikel über MDN . Wenn Sie nur die Rohbits möchten, entfernen Sie einfach die letzte Teilungsoperation.
Eine andere Sache zu beachten sind die Einschränkungen von JS. Zahlen können nur ganze Ganzzahlen mit einer Auflösung von bis zu 53 Bit darstellen. Bei Verwendung von bitweisen Operationen wird dies auf 32 reduziert. Dies macht es schwierig, in C oder C ++ geschriebene Algorithmen zu implementieren, die 64-Bit-Zahlen verwenden. Für die Portierung von 64-Bit-Code sind Shims erforderlich, die die Leistung drastisch reduzieren können. Der Einfachheit und Effizienz halber habe ich nur Algorithmen in Betracht gezogen, die 32-Bit-Mathematik verwenden, da diese direkt mit JS kompatibel ist.
Nun zu den Generatoren. (Ich pflege die vollständige Liste mit Referenzen hier )
sfc32 ist Teil der PractRand- Suite zum Testen von Zufallszahlen (die es natürlich besteht). sfc32 hat einen 128-Bit-Status und ist in JS sehr schnell.
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 ist ein einfacher Generator mit einem 32-Bit-Status, der jedoch extrem schnell und von guter Qualität ist (der Autor gibt an, dass er alle Tests der gjrand- Testsuite besteht und einen vollständigen Zeitraum von 2 bis 32 hat, den ich jedoch nicht überprüft habe).
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;
}
}
Ich würde dies empfehlen, wenn Sie nur ein einfaches, aber anständiges PRNG benötigen und keine Milliarden von Zufallszahlen benötigen (siehe Geburtstagsproblem ).
Seit Mai 2018 ist xoshiro128 ** das neue Mitglied der Xorshift-Familie von Vigna / Blackman (der auch xoroshiro geschrieben hat, das in Chrome verwendet wird). Es ist der schnellste Generator, der einen 128-Bit-Status bietet.
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;
}
}
Die Autoren behaupten, dass es Zufälligkeitstests gut besteht ( wenn auch mit Einschränkungen ). Andere Forscher haben darauf hingewiesen, dass einige Tests in TestU01 (insbesondere LinearComp und BinaryRank) nicht bestanden wurden. In der Praxis sollte es keine Probleme verursachen, wenn Floats verwendet werden (wie diese Implementierungen), kann jedoch Probleme verursachen, wenn Sie sich auf die rohen Low-Bits verlassen.
Dies ist JSF oder 'smallprng' von Bob Jenkins (2007), dem Typ, der ISAAC und SpookyHash gemacht hat . Es geht PractRand Tests und soll recht schnell, wenn auch nicht so schnell wie SFC sein.
function jsf32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}
LCG ist extrem schnell und einfach, aber die Qualität seiner Zufälligkeit ist so gering, dass eine unsachgemäße Verwendung tatsächlich Fehler in Ihrem Programm verursachen kann! Trotzdem ist es deutlich besser als einige Antworten, die vorschlagen, Math.sin
oder zu verwenden Math.PI
! Es ist allerdings ein Einzeiler, was schön ist :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Diese Implementierung wird als Minimalstandard- RNG bezeichnet, wie von Park-Miller 1988 und 1993 vorgeschlagen und in C ++ 11 als implementiert minstd_rand
. Beachten Sie, dass der Status 31 Bit ist (31 Bit ergeben 2 Milliarden mögliche Zustände, 32 Bit ergeben das Doppelte). Dies ist genau die Art von PRNG, die andere zu ersetzen versuchen!
Es wird funktionieren, aber ich würde es nicht verwenden, es sei denn, Sie brauchen wirklich Geschwindigkeit und kümmern sich nicht um die Zufallsqualität (was ist überhaupt zufällig?). Ideal für einen Game Jam oder eine Demo oder so. LCGs leiden unter Samenkorrelationen, daher ist es am besten, das erste Ergebnis zu verwerfen einer LCG . Und wenn Sie darauf bestehen, eine LCG zu verwenden, kann das Hinzufügen eines Inkrementwerts die Ergebnisse verbessern, aber es ist wahrscheinlich eine Übung der Sinnlosigkeit, wenn es viel bessere Optionen gibt.
Es scheint andere Multiplikatoren zu geben, die einen 32-Bit-Zustand (vergrößerter Zustandsraum) anbieten:
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Diese LCG-Werte stammen von: P. L'Ecuyer: Eine Tabelle von linearen Kongruenzgeneratoren unterschiedlicher Größe und guter Gitterstruktur, 30. April 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
ermöglicht einen Überlauf wie bei der Multiplikation in C mit 32-Bit-Ganzzahlen. Was Sie vorschlagen, ist eine LCG, die den gesamten Bereich des JS-Integer-Raums nutzt. Dies ist definitiv auch ein interessantes Gebiet, das es zu erkunden gilt. :)
Nein, aber hier ist ein einfacher Pseudozufallsgenerator, eine Implementierung von Multiply-with-Carry, die ich aus Wikipedia übernommen habe (wurde seitdem entfernt):
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: Behebung der
Startfunktion durch Zurücksetzen von m_z
EDIT2: Schwerwiegende Implementierungsfehler wurden behoben
seed
Funktion setzt den Zufallsgenerator nicht zurück, da die mz_z
Variable beim random()
Aufruf geändert wird. Setzen Sie daher mz_z = 987654321
(oder einen anderen Wert) inseed
m_w
, nicht m_z
. 2) Beide m_w
und werden m_z
basierend auf ihren vorherigen Werten geändert, sodass das Ergebnis geändert wird.
Der Algorithmus von Antti Sykäri ist nett und kurz. Ich habe anfangs eine Variation vorgenommen, die Math.random von Javascript ersetzte, wenn Sie Math.seed (s) aufrufen, aber dann bemerkte Jason, dass die Rückgabe der Funktion besser wäre:
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());
Dies gibt Ihnen eine weitere Funktionalität, die Javascript nicht hat: mehrere unabhängige Zufallsgeneratoren. Dies ist besonders wichtig, wenn mehrere wiederholbare Simulationen gleichzeitig ausgeführt werden sollen.
Math.random
, dass Sie mehrere unabhängige Generatoren haben, oder?
Math.seed(42);
es setzt die Funktion, wenn Sie also tun var random = Math.seed(42); random(); random();
erhalten Sie 0.70...
, dann 0.38...
. Wenn Sie es zurücksetzen, indem Sie var random = Math.seed(42);
erneut anrufen, erhalten random()
Sie beim nächsten Anruf 0.70...
erneut und beim nächsten Mal 0.38...
erneut.
random
anstatt eine native Javascript-Funktion zu überschreiben. Das Überschreiben Math.random
kann dazu führen, dass der JIST-Compiler den gesamten Code nicht optimiert.
Bitte sehen Sie Pierre L'Ecuyers Arbeiten, die bis in die späten 1980er und frühen 1990er Jahre zurückreichen. Es gibt auch andere. Wenn Sie kein Experte sind, ist es ziemlich gefährlich, selbst einen (Pseudo-) Zufallszahlengenerator zu erstellen, da die Wahrscheinlichkeit hoch ist, dass die Ergebnisse statistisch nicht zufällig sind oder nur einen kurzen Zeitraum haben. Pierre (und andere) haben einige gute (Pseudo-) Zufallszahlengeneratoren zusammengestellt, die einfach zu implementieren sind. Ich benutze einen seiner LFSR-Generatoren.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
Wenn Sie einige der vorherigen Antworten kombinieren, ist dies die gesetzte Zufallsfunktion, nach der Sie suchen:
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();
Math.seed(0)()
return 0.2322845458984375
und Math.seed(1)()
return 0.23228873685002327
. Beides m_w
und m_z
je nach Samen zu ändern scheint zu helfen. var m_w = 987654321 + s; var m_z = 123456789 - s;
erzeugt eine schöne Verteilung der ersten Werte mit verschiedenen Samen.
Es ist ganz einfach, einen eigenen Pseudozufallsgenerator zu schreiben.
Der Vorschlag von Dave Scotese ist nützlich, aber, wie andere betonten, nicht ganz gleichmäßig verteilt.
Es liegt jedoch nicht an den ganzzahligen Argumenten der Sünde. Es liegt einfach an der Reichweite der Sünde, die zufällig eine eindimensionale Projektion eines Kreises ist. Wenn Sie stattdessen den Winkel des Kreises nehmen würden, wäre er gleichmäßig.
Verwenden Sie also anstelle von sin (x) arg (exp (i * x)) / (2 * PI).
Wenn Ihnen die lineare Reihenfolge nicht gefällt, mischen Sie sie ein wenig mit xor. Der eigentliche Faktor spielt auch keine Rolle.
Um n Pseudozufallszahlen zu erzeugen, könnte man den folgenden Code verwenden:
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))
Bitte beachten Sie auch, dass Sie keine Pseudozufallssequenzen verwenden können, wenn echte Entropie benötigt wird.
Viele Leute, die heutzutage einen Startwertgenerator in Javascript benötigen, verwenden das Startwert-Zufallsmodul von David Bau .
Math.random
Nein, aber die Ran- Bibliothek löst dies. Es hat fast alle denkbaren Verteilungen und unterstützt die Erzeugung von gesetzten Zufallszahlen. Beispiel:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
Ich habe eine Funktion geschrieben, die eine gesetzte Zufallszahl zurückgibt. Sie verwendet Math.sin, um eine lange Zufallszahl zu haben, und verwendet den Startwert, um Zahlen daraus auszuwählen.
Verwenden :
seedRandom("k9]:2@", 15)
Es gibt Ihre gesetzte Zahl zurück. Der erste Parameter ist ein beliebiger Zeichenfolgenwert. dein Same. Der zweite Parameter gibt an, wie viele Ziffern zurückgegeben werden.
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)
}
Ein einfacher Ansatz für einen festen Samen:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Für eine Zahl zwischen 0 und 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
, dass jedes Mal, wenn Math.random
mit demselben Samen gesät wird, dieselbe aufeinanderfolgende Reihe von Zufallszahlen erzeugt wird. Bei dieser Frage geht es nicht um die tatsächliche Verwendung / Demonstration von Math.random
.