Sembrando el generador de números aleatorios en Javascript

373

¿Es posible sembrar el generador de números aleatorios (Math.random) en Javascript?

llorón
fuente
no está claro si desea sembrarlo para obtener los mismos resultados repetidamente para diferentes ejecuciones de prueba o si desea sembrarlo con 'algo único' por usuario para una mejor aleatoriedad entre el uso.
simbo1905
2
No, desafortunadamente no es posible. jsrand es una pequeña biblioteca que escribí cuando necesitaba un PRNG visible. También hay otras bibliotecas más complejas que puedes encontrar buscando en Google.
Domenico De Felice
44
Además de la pregunta: ¿cómo es posiblemente una buena idea ofrecer un PRNG sin un medio para sembrarlo? ¿Hay alguna buena razón para esto?
Alan
Ver también stackoverflow.com/questions/424292
Palimondo

Respuestas:

160

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 seedcualquier 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.

Antti Kissaniemi
fuente
20
¿Hay alguna manera de demostrar que este RNG genera números que están distribuidos uniformemente? Experimentalmente parece: jsfiddle.net/bhrLT
Nathan Breit
66
6,000,000 de operaciones / segundo es bastante rápido, no planeo generar más de ~ 3,000,000 por clic. Broma, esto es brillante.
AMK
59
-1, esta no es una muestra uniforme en absoluto: está bastante sesgada hacia 0 y 1 (ver jsfiddle.net/bhrLT/17 , que puede tomar un tiempo para calcular). Los valores consecutivos están correlacionados: cada 355 valores, y aún más cada 710, están relacionados. ¡Por favor, use algo más cuidadosamente pensado!
Spencer Nelson
38
La pregunta no se trata de crear un generador de números aleatorios criptográficamente seguro, sino algo que funcione en JavaScript, útil para demostraciones rápidas, etc. Tomaré algo rápido y simple que ofrezca una distribución atractiva de más de un millón de números aleatorios para ese propósito.
Jason Goemaat
15
Ten cuidado. Math.sin () puede dar resultados diferentes en el cliente y el servidor. Yo uso Meteor (usa javascript en cliente y servidor).
Obiwahn
145

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 xmur3produce 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 (Contador rápido simple)

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

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 ).

xoshiro128 **

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.

JSF (pequeño ayuno de Jenkins)

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 (también conocido como Lehmer / Park-Miller RNG o MCG)

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.sino 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.

bryc
fuente
55
Esta es una respuesta asombrosa. Seguro que volveré a esto.
DavidsKanal
1
Creo que los valores que citó en "Tablas de generadores congruentes lineales ..." de Pierre L'ecuyer podrían exceder el tamaño máximo de enteros en Javascript. La semilla máxima de (2 ^ 32-1) * 741103597 ≈ 3e18, que es mayor que el tamaño máximo int de JavaScript de ≈ 9e15. Creo que los siguientes valores del libro de Pierre tienen el período más grande dentro de los límites naturales: seed = (seed * 185852 + 1) % 34359738337.
Lachmanski
1
@Lachmanski es cierto, pero están vinculados por 32 bits (y el Park-Miller de 31 bits). El uso le Math.imulpermite 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. :)
bryc
1
¡Esto es asombroso! ¿Puedo copiar su sfc32 en un programa LGPL?
user334639
44
@ blobber2 no estoy seguro de lo que quieres decir, pero el código original es de aquí (con otros): github.com/bryc/code/blob/master/jshash/PRNGs.md . más o menos una esencia dentro de un repositorio :-)
bryc
39

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

Antti Kissaniemi
fuente
3
¿Alguien ha probado esta función por su aleatoriedad?
Justin
3
Este es el generador aleatorio de multiplicar con acarreo (MWC) con un período bastante largo. Adaptado de Wikipedia Generadores de números aleatorios
Michael_Scharf
10
La seedfunción no restablece el generador aleatorio, porque la mz_zvariable cambia cuando random()se llama. Por lo tanto, establezca mz_z = 987654321(o cualquier otro valor) enseed
Michael_Scharf
Cuando lo uso con mi generador de color aleatorio (HSL), genera solo colores verde y cian. El generador aleatorio original genera todos los colores. Entonces, no es lo mismo o no funciona.
Tomas Kubes
@Michael_Scharf 1) El cambio de semilla m_w, no m_z. 2) Ambos m_wy m_zse cambian BASADOS en sus valores anteriores, por lo que modifica el resultado.
ESL
26

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.


fuente
3
Si devuelve la función en lugar de establecer Math.randomeso le permitiría tener múltiples generadores independientes, ¿verdad?
Jason Goemaat
1
Asegúrese de ver los comentarios anteriores sobre la distribución de la aleatoriedad si eso es importante para usted: stackoverflow.com/questions/521295/…
jocull
¿Cómo se pueden repetir los randoms generados por esto? Sigue dando nuevos números cada vez
SMUsamaShah
cada vez que lo hace 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....
WOUNDEDStevenJones
1
Por favor no use esto. Tómese el tiempo para usar una variable local llamada en randomlugar de sobrescribir una función de JavaScript nativa. La sobrescritura Math.randompuede hacer que el compilador JIST no optimice todo su código.
Jack Giffin
11

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

usuario2383235
fuente
1
Gran respuesta, pero no está relacionado con javascript :)
Nikolay Fominyh
3
El código para implementar el trabajo del profesor L'Ecuyer está disponible públicamente para Java y la mayoría de los programadores pueden traducirlo fácilmente a Javascript.
user2383235
6

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();
usuario3158327
fuente
44
Esto produce resultados muy similares al comienzo de la secuencia con diferentes semillas. Por ejemplo, Math.seed(0)()devoluciones 0.2322845458984375y Math.seed(1)()devoluciones 0.23228873685002327. Cambiar ambos m_wy de m_zacuerdo 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.
indefinido
1
@undefined el problema que describió se solucionó a partir de la última edición, fue un error en la implementación de MWC.
bryc
Funciona bien ahora, a partir de enero de 2020. Semilla con 0, obtenga 0.7322976540308446. Semilla con 1, 0.16818441334180534, con 2: 0.6040864314418286, con 3: 0.03998844954185188. ¡Gracias a los dos!
Eureka
3

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.

Lajos Bodrogi
fuente
No soy un experto, pero las semillas secuenciales siguen un patrón constante . Los píxeles de colores son> = 0.5. ¿Supongo que solo está iterando sobre el radio una y otra vez?
bryc
2

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 .

Martin Omander
fuente
1

Math.randomno, 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)
Ulf Aslak
fuente
-1

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)
      }
Tyler Hudson
fuente
1
Las secuencias de números producidas por esto realmente no se aproximan a las propiedades de las secuencias de números aleatorios. Genere 15 números con él y la cadena resultante casi siempre comienza con un 7 para casi cualquier tecla, por ejemplo.
Gabriel
-2

Un enfoque simple para una semilla fija:

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}
Carlos Oliveira
fuente
-6

Para un número entre 0 y 100.

Number.parseInt(Math.floor(Math.random() * 100))
Lord Elrond
fuente
3
La pregunta es acerca de la siembra de Math.randommodo que siempre que Math.randomse 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.
Jack Giffin