¿Es posible sembrar el generador de números aleatorios (Math.random) en Javascript?
¿Es posible sembrar el generador de números aleatorios (Math.random) en Javascript?
Respuestas:
No, no lo es, pero es bastante fácil escribir su propio generador, o mejor aún usar uno existente. Echa un vistazo: esta pregunta relacionada .
Además, vea el blog de David Bau para obtener más información sobre la siembra .
NOTA: A pesar de (o más bien, debido a) la concisión y la elegancia aparente, este algoritmo no es de ninguna manera de alta calidad en términos de aleatoriedad. Busque, por ejemplo, los que figuran en esta respuesta para obtener mejores resultados.
(Originalmente adaptado de una idea inteligente presentada en un comentario a otra respuesta).
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Puede establecer seed
cualquier número, solo evite cero (o cualquier múltiplo de Math.PI).
La elegancia de esta solución, en mi opinión, proviene de la falta de números "mágicos" (además de 10000, que representa aproximadamente la cantidad mínima de dígitos que debe tirar para evitar patrones extraños; vea los resultados con valores 10 , 100 , 1000 ) La brevedad también es agradable.
Es un poco más lento que Math.random () (por un factor de 2 o 3), pero creo que es casi tan rápido como cualquier otra solución escrita en JavaScript.
He implementado varias funciones buenas, cortas y rápidas de generador de números pseudoaleatorios (PRNG) en JavaScript simple. Todos ellos pueden sembrarse y proporcionar números de buena calidad.
En primer lugar, tenga cuidado de inicializar sus PRNG correctamente. La mayoría de los generadores a continuación no tienen un procedimiento de generación de semillas incorporado (por simplicidad), pero aceptan uno o más valores de 32 bits como el estado inicial del PRNG. Semillas similares (por ejemplo, una semilla simple de 1 y 2) pueden causar correlaciones en PRNG más débiles, lo que da como resultado que la salida tenga propiedades similares (como que los niveles generados aleatoriamente sean similares). Para evitar esto, es una buena práctica inicializar los PRNG con una semilla bien distribuida.
Afortunadamente, las funciones hash son muy buenas para generar semillas para PRNG a partir de cadenas cortas. Una buena función hash generará resultados muy diferentes incluso cuando dos cadenas sean similares. Aquí hay un ejemplo basado en la función de mezcla 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;
}
}
Cada llamada posterior a la función de retorno de xmur3
produce un nuevo valor hash "aleatorio" de 32 bits que se utilizará como semilla en un PRNG. Así es como puede 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();
Alternativamente, simplemente elija algunos datos ficticios para rellenar la semilla y avance el generador varias veces (12-20 iteraciones) para mezclar bien el estado inicial. Esto se ve a menudo en implementaciones de referencia de PRNG, pero limita el número de estados iniciales.
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();
La salida de estas funciones PRNG produce un número positivo de 32 bits (0 a 2 32 -1) que luego se convierte en un número de coma flotante entre 0-1 (0 inclusive, 1 exclusivo) equivalente a Math.random()
, si desea números aleatorios de un rango específico, lea este artículo en MDN . Si solo desea los bits sin procesar, simplemente elimine la operación de división final.
Otra cosa a tener en cuenta son las limitaciones de JS. Los números solo pueden representar enteros enteros de hasta 53 bits de resolución. Y cuando se utilizan operaciones bit a bit, esto se reduce a 32. Esto dificulta la implementación de algoritmos escritos en C o C ++, que usan números de 64 bits. Portar código de 64 bits requiere cuñas que pueden reducir drásticamente el rendimiento. Entonces, por simplicidad y eficiencia, solo he considerado algoritmos que usan matemáticas de 32 bits, ya que es directamente compatible con JS.
Ahora, adelante a los generadores. (Mantengo la lista completa con referencias aquí )
sfc32 es parte del conjunto de pruebas de números aleatorios PractRand (que, por supuesto, pasa). sfc32 tiene un estado de 128 bits y es muy rápido en 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 es un generador simple con un estado de 32 bits, pero es extremadamente rápido y de buena calidad (el autor declara que pasa todas las pruebas de gjrand testing suite y tiene un período completo de 2 32 , pero no lo he verificado).
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 recomendaría si solo necesita un PRNG simple pero decente y no necesita miles de millones de números aleatorios (consulte Problema de cumpleaños ).
A partir de mayo de 2018, xoshiro128 ** es el nuevo miembro de la familia Xorshift , de Vigna / Blackman (quien también escribió xoroshiro, que se usa en Chrome). Es el generador más rápido que ofrece un estado de 128 bits.
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;
}
}
Los autores afirman que pasa bien las pruebas de aleatoriedad ( aunque con advertencias ). Otros investigadores han señalado que falla algunas pruebas en TestU01 (particularmente LinearComp y BinaryRank). En la práctica, no debería causar problemas cuando se utilizan flotadores (como estas implementaciones), pero puede causar problemas si se confía en los bits bajos sin procesar.
Este es JSF o 'smallprng' de Bob Jenkins (2007), el tipo que creó ISAAC y SpookyHash . Se pasa las pruebas PractRand y debe ser bastante rápido, aunque no tan rápido como 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 es extremadamente rápido y simple, pero la calidad de su aleatoriedad es tan baja que el uso incorrecto puede causar errores en su programa. Sin embargo, ¡es significativamente mejor que algunas respuestas que sugieren usar Math.sin
o Math.PI
! Sin embargo, es una frase, lo cual es bueno :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Esta implementación se denomina RNG estándar mínimo según lo propuesto por Park-Miller en 1988 y 1993 y se implementa en C ++ 11 como minstd_rand
. Tenga en cuenta que el estado es de 31 bits (31 bits dan 2 mil millones de estados posibles, 32 bits dan el doble de eso). ¡Este es el tipo de PRNG que otros están tratando de reemplazar!
Funcionará, pero no lo usaría a menos que realmente necesite velocidad y no le importe la calidad de la aleatoriedad (¿qué es al azar de todos modos?). Genial para un juego jam o una demo o algo así. Los LCG sufren correlaciones de semillas, por lo que es mejor descartar el primer resultado de un LCG. Y si insiste en usar un LCG, agregar un valor de incremento puede mejorar los resultados, pero probablemente sea un ejercicio inútil cuando existen opciones mucho mejores.
Parece que hay otros multiplicadores que ofrecen un estado de 32 bits (mayor espacio de estado):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Estos valores LCG son de: P. L'Ecuyer: una tabla de generadores congruentes lineales de diferentes tamaños y buena estructura reticular, 30 de abril de 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
permite desbordarse como lo haría al usar la multiplicación en C en enteros de 32 bits. Lo que está sugiriendo es un LCG que utiliza la gama completa del espacio entero de JS, que definitivamente es un área interesante para explorar también. :)
No, pero aquí hay un generador pseudoaleatorio simple, una implementación de Multiplicar con acarreo que adapté de Wikipedia (se ha eliminado desde entonces):
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: fija la función de semilla por lo que es restablecer m_z
Edit2: defectos graves de ejecución se han fijado
seed
función no restablece el generador aleatorio, porque la mz_z
variable cambia cuando random()
se llama. Por lo tanto, establezca mz_z = 987654321
(o cualquier otro valor) enseed
m_w
, no m_z
. 2) Ambos m_w
y m_z
se cambian BASADOS en sus valores anteriores, por lo que modifica el resultado.
El algoritmo de Antti Sykäri es agradable y corto. Inicialmente hice una variación que reemplazó Math.random de Javascript cuando llamas a Math.seed (s), pero luego Jason comentó que devolver la función sería mejor:
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());
Esto le brinda otra funcionalidad que Javascript no tiene: múltiples generadores aleatorios independientes. Eso es especialmente importante si desea tener múltiples simulaciones repetibles ejecutándose al mismo tiempo.
Math.random
eso le permitiría tener múltiples generadores independientes, ¿verdad?
Math.seed(42);
se restablece la función, así que si no var random = Math.seed(42); random(); random();
se obtiene 0.70...
, a continuación 0.38...
. Si lo reinicia llamando de var random = Math.seed(42);
nuevo, la próxima vez que llame random()
recibirá de 0.70...
nuevo y la próxima vez que lo haga 0.38...
.
random
lugar de sobrescribir una función de JavaScript nativa. La sobrescritura Math.random
puede hacer que el compilador JIST no optimice todo su código.
Vea el trabajo de Pierre L'Ecuyer que se remonta a finales de los años ochenta y principios de los noventa. Hay otros también. Crear un generador de números (pseudo) aleatorio por su cuenta, si no es un experto, es bastante peligroso, porque existe una alta probabilidad de que los resultados no sean estadísticamente aleatorios o que tengan un período pequeño. Pierre (y otros) han reunido algunos buenos (pseudo) generadores de números aleatorios que son fáciles de implementar. Yo uso uno de sus generadores LFSR.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
Combinando algunas de las respuestas anteriores, esta es la función aleatoria visible que está buscando:
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)()
devoluciones 0.2322845458984375
y Math.seed(1)()
devoluciones 0.23228873685002327
. Cambiar ambos m_w
y de m_z
acuerdo con la semilla parece ayudar. var m_w = 987654321 + s; var m_z = 123456789 - s;
produce una buena distribución de los primeros valores con diferentes semillas.
Escribir su propio generador pseudoaleatorio es bastante simple.
La sugerencia de Dave Scotese es útil pero, como han señalado otros, no está distribuida de manera uniforme.
Sin embargo, no se debe a los argumentos enteros del pecado. Es simplemente por el rango del pecado, que resulta ser una proyección unidimensional de un círculo. Si tomas el ángulo del círculo, sería uniforme.
Entonces, en lugar de sin (x) use arg (exp (i * x)) / (2 * PI).
Si no te gusta el orden lineal, mézclalo un poco con xor. El factor real tampoco importa tanto.
Para generar n números seudoaleatorios, se podría usar el código:
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))
Tenga en cuenta también que no puede usar secuencias pseudoaleatorias cuando se necesita una entropía real.
Muchas personas que necesitan un generador de números aleatorios visibles en Javascript en estos días están utilizando el módulo de semilla aleatoria de David Bau .
Math.random
no, pero la biblioteca ejecutada resuelve esto. Tiene casi todas las distribuciones que puedas imaginar y admite la generación de números aleatorios sembrados. Ejemplo:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
He escrito una función que devuelve un número aleatorio sembrado, usa Math.sin para tener un número aleatorio largo y usa la semilla para elegir números de eso.
Utilizar :
seedRandom("k9]:2@", 15)
devolverá su número sembrado; el primer parámetro es cualquier valor de cadena; tu semilla El segundo parámetro es cuántos dígitos volverán.
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 enfoque simple para una semilla fija:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Para un número entre 0 y 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
modo que siempre que Math.random
se siembra con la misma semilla, produzca la misma serie sucesiva de números aleatorios. Esta pregunta no es, por decir, sobre el uso / demostración real de Math.random
.