È possibile eseguire il seeding del generatore di numeri casuali (Math.random) in Javascript?
È possibile eseguire il seeding del generatore di numeri casuali (Math.random) in Javascript?
Risposte:
No, non lo è, ma è abbastanza facile scrivere il proprio generatore, o meglio usarne uno esistente. Scopri: questa domanda correlata .
Inoltre, consultare il blog di David Bau per ulteriori informazioni sulla semina .
NOTA: Nonostante (o meglio, a causa di) succinta e apparente eleganza, questo algoritmo non è affatto di alta qualità in termini di casualità. Cerca ad esempio quelli elencati in questa risposta per risultati migliori.
(Originariamente adattato da un'idea intelligente presentata in un commento a un'altra risposta.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Puoi impostare seed
qualsiasi numero, evita semplicemente zero (o qualsiasi multiplo di Math.PI).
L'eleganza di questa soluzione, secondo me, deriva dalla mancanza di numeri "magici" (oltre a 10000, che rappresenta circa la quantità minima di cifre che è necessario eliminare per evitare schemi dispari - vedere i risultati con valori 10 , 100 , 1000 ). Anche la brevità è bella.
È un po 'più lento di Math.random () (di un fattore 2 o 3), ma credo che sia più veloce di qualsiasi altra soluzione scritta in JavaScript.
Ho implementato una serie di funzioni buone, brevi e veloci del generatore di numeri pseudocasuali (PRNG) in JavaScript. Tutti possono essere seminati e fornire numeri di buona qualità.
Prima di tutto, assicurati di inizializzare correttamente i tuoi PRNG. La maggior parte dei generatori di seguito non ha una procedura di generazione dei seed incorporata (per semplicità), ma accetta uno o più valori a 32 bit come stato iniziale del PRNG. Semi simili (ad es. Un seme semplice di 1 e 2) possono causare correlazioni in PRNG più deboli, con conseguente output con proprietà simili (come livelli generati casualmente essere simili). Per evitare ciò, è consigliabile inizializzare i PRNG con un seme ben distribuito.
Per fortuna, le funzioni di hash sono molto buone nel generare semi per PRNG da stringhe brevi. Una buona funzione hash genererà risultati molto diversi anche quando due stringhe sono simili. Ecco un esempio basato sulla funzione di missaggio di 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;
}
}
Ogni chiamata successiva alla funzione return di xmur3
produce un nuovo valore hash "casuale" a 32 bit da utilizzare come seed in un PRNG. Ecco come potresti usarlo:
// 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();
In alternativa, è sufficiente scegliere alcuni dati fittizi con cui riempire il seme e far avanzare il generatore alcune volte (12-20 iterazioni) per mescolare accuratamente lo stato iniziale. Questo è spesso visto nelle implementazioni di riferimento dei PRNG, ma limita il numero di stati iniziali.
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();
L'output di queste funzioni PRNG produce un numero positivo a 32 bit (da 0 a 2 32 -1) che viene quindi convertito in un numero a virgola mobile compreso tra 0-1 (0 incluso, 1 esclusivo) equivalente Math.random()
, se si desidera numeri casuali di un intervallo specifico, leggi questo articolo su MDN . Se si desidera solo i bit grezzi, è sufficiente rimuovere l'operazione di divisione finale.
Un'altra cosa da notare sono i limiti di JS. I numeri possono rappresentare solo numeri interi con una risoluzione fino a 53 bit. E quando si usano operazioni bit a bit, questo è ridotto a 32. Ciò rende difficile implementare algoritmi scritti in C o C ++, che usano numeri a 64 bit. Il porting del codice a 64 bit richiede shim che possono ridurre drasticamente le prestazioni. Quindi, per semplicità ed efficienza, ho preso in considerazione solo algoritmi che usano la matematica a 32 bit, poiché è direttamente compatibile con JS.
Ora, in seguito ai generatori. (Mantengo l'elenco completo con riferimenti qui )
sfc32 fa parte della suite di test di numeri casuali di PractRand (che passa ovviamente). sfc32 ha uno stato a 128 bit ed è molto veloce in JS.
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 è un semplice generatore con uno stato a 32 bit, ma è estremamente veloce e di buona qualità (l'autore afferma che supera tutti i test della suite di test gjrand e ha un periodo completo di 2 32 , ma non ho verificato).
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;
}
}
Lo consiglierei se hai solo bisogno di un PRNG semplice ma decente e non hai bisogno di miliardi di numeri casuali (vedi Problema di compleanno ).
A partire da maggio 2018, xoshiro128 ** è il nuovo membro della famiglia Xorshift , di Vigna / Blackman (che ha anche scritto xoroshiro, che viene utilizzato in Chrome). È il generatore più veloce che offre uno stato a 128 bit.
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;
}
}
Gli autori sostengono che supera bene i test di casualità ( anche se con avvertimenti ). Altri ricercatori hanno sottolineato che falliscono alcuni test in TestU01 (in particolare LinearComp e BinaryRank). In pratica, non dovrebbe causare problemi quando si utilizzano float (come queste implementazioni), ma può causare problemi se si fa affidamento sui bit bassi non elaborati.
Questo è JSF o 'smallprng' di Bob Jenkins (2007), il ragazzo che ha realizzato ISAAC e SpookyHash . Si passa test PractRand e dovrebbe essere abbastanza veloce, anche se non così velocemente come SFC.
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 è estremamente veloce e semplice, ma la qualità della sua casualità è così bassa che un uso improprio può effettivamente causare bug nel tuo programma! Tuttavia, è significativamente meglio di alcune risposte che suggeriscono di usare Math.sin
o Math.PI
! È un one-liner però, che è bello :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Questa implementazione è chiamata RNG standard minima come proposta da Park-Miller nel 1988 e 1993 e implementata in C ++ 11 come minstd_rand
. Tieni presente che lo stato è 31 bit (31 bit danno 2 miliardi di stati possibili, 32 bit danno il doppio). Questo è il tipo di PRNG che altri stanno cercando di sostituire!
Funzionerà, ma non lo userei a meno che tu non abbia davvero bisogno di velocità e non ti interessi della qualità della casualità (che cos'è comunque casuale?). Ottimo per un gioco marmellata o una demo o qualcosa del genere. I LCG soffrono di correlazioni con i semi, quindi è meglio scartare il primo risultato di un LCG. E se insisti nell'utilizzare un LCG, l'aggiunta di un valore di incremento può migliorare i risultati, ma è probabilmente un esercizio di futilità quando esistono opzioni molto migliori.
Sembra che ci siano altri moltiplicatori che offrono uno stato a 32 bit (spazio di stato aumentato):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Questi valori LCG provengono da: P. L'Ecuyer: una tabella di generatori congruenziali lineari di diverse dimensioni e buona struttura reticolare, 30 aprile 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
consente di overflow come farebbe quando si utilizza la moltiplicazione in C su numeri interi a 32 bit. Quello che stai suggerendo è un LCG che utilizza l'intera gamma dello spazio intero di JS, che è sicuramente un'area interessante da esplorare. :)
No, ma ecco un semplice generatore pseudocasuale, un'implementazione di Moltiplica con carry adattata da Wikipedia (rimossa da allora):
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: risolta la funzione seed ripristinandola m_z
EDIT2: sono stati corretti gravi problemi di implementazione
seed
funzione non reimposta il generatore casuale, poiché la mz_z
variabile viene modificata quando random()
viene chiamata. Quindi imposta mz_z = 987654321
(o qualsiasi altro valore) inseed
m_w
, no m_z
. 2) Entrambi m_w
e m_z
cambiano in base ai loro valori precedenti, quindi modifica il risultato.
L'algoritmo di Antti Sykäri è bello e breve. Inizialmente ho fatto una variante che ha sostituito Math.random di Javascript quando chiami Math.seed (s), ma poi Jason ha commentato che restituire la funzione sarebbe meglio:
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());
Questo ti dà un'altra funzionalità che Javascript non ha: più generatori casuali indipendenti. Ciò è particolarmente importante se si desidera avere più simulazioni ripetibili in esecuzione contemporaneamente.
Math.random
che ti consentirebbe di avere più generatori indipendenti, giusto?
Math.seed(42);
si resetta la funzione, quindi se lo fai var random = Math.seed(42); random(); random();
si ottiene 0.70...
, quindi 0.38...
. Se lo ripristini chiamando di var random = Math.seed(42);
nuovo, la prossima volta che chiami random()
riceverai di 0.70...
nuovo e la prossima volta che riceverai di 0.38...
nuovo.
random
anziché sovrascrivere una funzione javascript nativa. La sovrascrittura Math.random
può far sì che il compilatore JIST non ottimizzi tutto il codice.
Si prega di vedere il lavoro di Pierre L'Ecuyer che risale alla fine degli anni '80 e all'inizio degli anni '90. Ce ne sono anche altri. Creare un generatore di numeri (pseudo) casuale da solo, se non sei un esperto, è piuttosto pericoloso, perché c'è un'alta probabilità che i risultati non siano statisticamente casuali o che abbiano un breve periodo. Pierre (e altri) hanno messo insieme dei buoni (pseudo) generatori di numeri casuali che sono facili da implementare. Uso uno dei suoi generatori LFSR.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
Combinando alcune delle risposte precedenti, questa è la funzione casuale seminabile che stai cercando:
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)()
restituisce 0.2322845458984375
e Math.seed(1)()
restituisce 0.23228873685002327
. Cambiare entrambi m_w
e in m_z
base al seme sembra aiutare. var m_w = 987654321 + s; var m_z = 123456789 - s;
produce una buona distribuzione dei primi valori con semi diversi.
Scrivere il tuo generatore pseudo casuale è abbastanza semplice.
Il suggerimento di Dave Scotese è utile ma, come sottolineato da altri, non è distribuito in modo abbastanza uniforme.
Tuttavia, non è a causa degli argomenti interi del peccato. È semplicemente a causa della gamma del peccato, che sembra essere una proiezione unidimensionale di un cerchio. Se invece prendessi l'angolo del cerchio sarebbe uniforme.
Quindi invece di sin (x) usa arg (exp (i * x)) / (2 * PI).
Se non ti piace l'ordine lineare, mescola un po 'con xor. Anche il fattore reale non conta molto.
Per generare n numeri pseudo casuali è possibile utilizzare il codice:
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))
Si noti inoltre che non è possibile utilizzare sequenze pseudo casuali quando è necessaria la vera entropia.
Molte persone che hanno bisogno di un generatore di numeri casuali seedable in Javascript in questi giorni utilizzano il modulo seedrandom di David Bau .
Ho scritto una funzione che restituisce un numero casuale con seeding, usa Math.sin per avere un numero casuale lungo e usa il seed per scegliere i numeri da quello.
Uso :
seedRandom("k9]:2@", 15)
restituirà il numero di seeding il primo parametro è qualsiasi valore di stringa; il tuo seme. il secondo parametro è quante cifre restituiranno.
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)
}
Un approccio semplice per un seme fisso:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Per un numero compreso tra 0 e 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
tale che ogni volta che Math.random
viene seminato con lo stesso seme, produrrà la stessa serie successiva di numeri casuali. Questa domanda non è, per dire, sull'uso / dimostrazione effettiva di Math.random
.