Gerador de número aleatório JavaScript propagável

149

A Math.random()função JavaScript retorna um valor aleatório entre 0 e 1, semeado automaticamente com base no horário atual (semelhante ao Java, acredito). No entanto, acho que não há como definir sua própria semente.

Como posso criar um gerador de números aleatórios para o qual posso fornecer meu próprio valor inicial, para que ele produza uma sequência repetível de (pseudo) números aleatórios?

scunliffe
fonte
1
Nota: No intuito de manter esta pergunta curta e focada, dividi o código que estava na pergunta acima em uma resposta do Wiki da comunidade abaixo.
Ilmari Karonen 10/03/2014
1
Veja também stackoverflow.com/questions/521295
Palimondo

Respostas:

123

Uma opção é http://davidbau.com/seedrandom, que é um substituto substituto do Math.random () baseado em RC4 e com boas propriedades.

David Bau
fonte
18
Desde então, o seedrandom de David Bau se tornou popular o suficiente para que ele seja mantido aqui no github . É uma pena que o ECMAScript tenha sido o cenário por tanto tempo que coisas assim não estão incluídas no idioma. Sério, sem propagação !!!
Coma em Joes
2
@EatatJoes, é tanto a vergonha quanto a glória de JS que isso seja necessário e possível. É muito legal que você possa incluir um arquivo e obter alterações compatíveis com versões anteriores feitas no objeto Math. Nada mal por 10 dias de trabalho, Brendan Eich.
Bruno Bronosky
2
Para quem procura a página de npm para este projeto: npmjs.com/package/seedrandom
Kip
27

Se você não precisar da capacidade de propagação, use Math.random()e crie funções auxiliares em torno dela (por exemplo randRange(start, end)).

Não sei ao certo qual RNG você está usando, mas é melhor conhecê-lo e documentá-lo para que você esteja ciente de suas características e limitações.

Como Starkii disse, Mersenne Twister é um bom PRNG, mas não é fácil de implementar. Se você quiser fazer isso, tente implementar um LCG - é muito fácil, possui qualidades de aleatoriedade decentes (não tão boas quanto Mersenne Twister), e você pode usar algumas das constantes populares.

EDIT: considere as ótimas opções nesta resposta para implementações de RNG semeadas curtas, incluindo uma opção de LCG.

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));

orip
fonte
2
O módulo não deve ser 2 ^ 31? Eu li esse algoritmo do wiki .
Trantor Liu
3
Só para você estar ciente, isso não é "correto" no sentido de que não gera o que a matemática exige. Em outras palavras, um idioma que possa lidar com esses números grandes terá um resultado diferente. JS engasga com a precisão dos números e das costeletas (afinal, são flutuadores).
DDS
4
-1 Esta implementação de LCG reduz o limite para números inteiros exatos em JavaScript, pois this.a * this.stateé provável que resulte em um número maior que 2 ^ 53. O resultado é uma faixa de produção limitada e, para algumas sementes, possivelmente um período muito curto. Além disso, em geral, usando uma potência de dois para mresultar em alguns padrões bastante óbvios, quando você está gastando uma operação de módulo em vez de um simples truncamento de qualquer maneira, não há razão para não usar um primo.
precisa
22

Se você deseja especificar a semente, basta substituir as chamadas getSeconds()egetMinutes() . Você pode passar um int e usar metade dele mod 60 para o valor de segundos e a outra metade módulo 60 para dar a outra parte.

Dito isto, esse método parece lixo. Fazer a geração apropriada de números aleatórios é muito difícil. O problema óbvio disso é que a semente do número aleatório é baseada em segundos e minutos. Para adivinhar a semente e recriar seu fluxo de números aleatórios, é necessário tentar 3600 combinações diferentes de segundo e minuto. Isso também significa que existem apenas 3600 diferentes sementes possíveis. Isso é corrigível, mas eu desconfiaria deste RNG desde o início.

Se você quiser usar um RNG melhor, tente o Mersenne Twister . É um RNG bem testado e bastante robusto, com uma órbita enorme e excelente desempenho.

Edição: Eu realmente deveria estar correto e me refiro a isso como um gerador de números pseudo-aleatórios ou PRNG.

"Quem usa métodos aritméticos para produzir números aleatórios está em estado de pecado".
                                                                                                                                                          --- John von Neumann

Starkii
fonte
1
Um link para implementações JS do Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip
1
@orip Você tem uma fonte para os 3600 estados iniciais? Mersenne Twister é propagada por um número de 32 bits, portanto o PRNG deve ter 4 bilhões de estados iniciais - apenas se a semente inicial for realmente aleatória.
Tobias P.
2
@TobiasP. Eu estava me referindo à sugestão de propagar com uma combinação de getSeconds () e getMinutes (), 60 * 60 == 3600 possíveis estados iniciais. Eu não estava me referindo a Mersenne Twister.
orip 01/08/2012
3
@ orip Ok, não estava claro. Você estava falando sobre Mersenne Twister e, na próxima frase, sobre estados iniciais;)
Tobias P.
2
O solicitante de pergunta não menciona que precisa da geração "adequada" de números aleatórios para qualquer tipo de aplicativo criptograficamente sensível. Embora toda a resposta seja verdadeira, apenas o primeiro parágrafo é realmente relevante para a pergunta. Talvez adicione um trecho de código da solução sugerida.
Rubinetti
12

Eu uso uma porta JavaScript do Mersenne Twister: https://gist.github.com/300494 Ele permite que você defina a semente manualmente. Além disso, como mencionado em outras respostas, o Mersenne Twister é realmente um bom PRNG.

Christoph Henkelmann
fonte
8

O código que você listou parece um Lehmer RNG . Se for esse o caso, então 2147483647é o maior número inteiro assinado de 32 bits, 2147483647o maior número primo de 32 bits e48271 um multiplicador de período completo usado para gerar os números.

Se isso for verdade, você pode modificar RandomNumberGeneratorpara obter um parâmetro extra seede definir this.seedcomo seed; mas você deve ter cuidado para garantir que a semente resulte em uma boa distribuição de números aleatórios (Lehmer pode ser estranho assim) - mas a maioria das sementes ficará bem.

mipadi
fonte
7

A seguir, um PRNG que pode ser alimentado com uma semente personalizada. A chamada SeedRandomretornará uma função geradora aleatória.SeedRandompode ser chamado sem argumentos para propagar a função aleatória retornada com o tempo atual ou pode ser chamado com 1 ou 2 interseções não-negativas como argumentos para propiciar a propagação com esses números inteiros. Devido ao ponto de flutuação, a precisão da propagação com apenas 1 valor permitirá apenas que o gerador seja iniciado em um dos 2 ^ 53 estados diferentes.

A função geradora aleatória retornada recebe 1 argumento inteiro nomeado limit; o limite deve estar no intervalo de 1 a 4294965886; a função retornará um número no intervalo de 0 ao limite 1.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

Exemplo de uso:

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

Este gerador exibe as seguintes propriedades:

  • Possui aproximadamente 2 ^ 64 diferentes estados internos possíveis.
  • Ele tem um período de aproximadamente 2 ^ 63, muito mais do que alguém jamais precisará realisticamente em um programa JavaScript.
  • Devido aos modvalores serem primos, não há um padrão simples na saída, independentemente do limite escolhido. Isso é diferente de alguns PRNGs mais simples que exibem alguns padrões bastante sistemáticos.
  • Ele descarta alguns resultados para obter uma distribuição perfeita, independentemente do limite.
  • É relativamente lento, roda cerca de 10 000 000 vezes por segundo na minha máquina.
aaaaaaaaaaaa
fonte
2
Por que isso produz um padrão? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski 01/12/16
@ TimothyKanski Porque você está usando errado. Não sou especialista, mas isso ocorre porque você está inicializando o gerador em cada iteração, vendo apenas seu primeiro valor com base na semente e NÃO repetindo nos números subseqüentes do gerador. Acredito que isso acontecerá em qualquer PRNG que não mistura a semente no intervalo especificado.
Bryc #
5

Se você programar em Typescript, adaptei a implementação de Mersenne Twister que foi trazida na resposta de Christoph Henkelmann a este segmento como uma classe de texto datilografado:

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.mti;
            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
            /* In the previous versions, MSBs of the seed affect   */
            /* only MSBs of the array mt[].                        */
            /* 2002/01/09 modified by Makoto Matsumoto             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

            if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                this.init_genrand(5489);
            /* a default initial seed is used */

            for (kk = 0; kk < Random.N - Random.M; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            for (; kk < Random.N - 1; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
            this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];

            this.mti = 0;
        }

        y = this.mt[this.mti++];

        /* Tempering */
        y ^= (y >>> 11);
        y ^= (y << 7) & 0x9d2c5680;
        y ^= (y << 15) & 0xefc60000;
        y ^= (y >>> 18);

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

você pode usá-lo da seguinte maneira:

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

verifique a fonte para mais métodos.

bennil
fonte
0

Nota: Este código foi originalmente incluído na pergunta acima. No interesse de manter a pergunta curta e focada, mudei-a para esta resposta do Wiki da Comunidade.

Eu encontrei esse código e parece funcionar bem para obter um número aleatório e depois usar a semente posteriormente, mas não tenho muita certeza de como a lógica funciona (por exemplo, de onde vieram os números 2345678901, 48271 e 2147483647).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/
Ilmari Karonen
fonte
3
Uau, as funções RandomNumberGeneratore nextRandomNumberrealmente datam de 1996. Supõe-se que seja um Lehmer / LCG RNG. Ele usa algumas matemáticas inteligentes para executar aritmética modular em números inteiros de 32 bits que, de outra forma, seriam muito pequenos para conter alguns valores intermediários. O problema é que o JavaScript não implementa números inteiros de 32 bits, mas sim números flutuantes de 64 bits, e como a divisão não é uma divisão inteira, como esse código presume que o resultado não é um gerador Lehmer. Produz algum resultado que parece aleatório, mas as garantias de um gerador Lehmer não se aplicam.
aaaaaaaaaaaa 16/03
1
A createRandomNumberfunção é uma adição posterior, faz praticamente tudo de errado, principalmente instancia um novo RNG toda vez que é chamado, o que significa que as chamadas em sucessão rápida usarão o mesmo flutuador. No código fornecido, é quase impossível 'a'ser emparelhado com qualquer coisa, exceto '1'e 'red'.
aaaaaaaaaaaa 16/03
-2

OK, aqui está a solução que eu decidi.

Primeiro, você cria um valor inicial usando a função "newseed ()". Então você passa o valor inicial para a função "srandom ()". Por fim, a função "srandom ()" retorna um valor pseudo-aleatório entre 0 e 1.

O ponto crucial é que o valor inicial é armazenado dentro de uma matriz. Se fosse simplesmente um número inteiro ou float, o valor seria sobrescrito toda vez que a função fosse chamada, uma vez que os valores de números inteiros, floats, strings e assim por diante são armazenados diretamente na pilha versus apenas os ponteiros, como no caso de matrizes e outros objetos. Assim, é possível que o valor da semente permaneça persistente.

Finalmente, é possível definir a função "srandom ()" de modo que seja um método do objeto "Math", mas deixarei isso para você descobrir. ;)

Boa sorte!

JavaScript:

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

Lua 4 (meu ambiente de destino pessoal):

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)
posfan12
fonte
PS - Ainda não estou familiarizado com o Stack Overflow, mas por que as postagens não estão em ordem cronológica?
posfan12
Oi @ posfan12 - as respostas às perguntas são normalmente listadas em ordem por "upvotes", de modo que o "creme chegue ao topo". No entanto, para garantir uma visualização justa das respostas com a mesma pontuação, elas são exibidas em ordem aleatória. Como essa era minha pergunta originalmente ;-) certamente terei a certeza de conferir em breve. Se eu (ou outras pessoas) achar esta resposta útil, vamos votá-la e, se eu achar que é a resposta "correta", você também verá uma marca de seleção verde. - Bem-vindo ao StackOverflow!
scunliffe
2
-1 Esta implementação de LCG reduz o limite para números inteiros exatos em JavaScript, pois seedobj[0] * seedobjaé provável que resulte em um número maior que 2 ^ 53. O resultado é uma faixa de produção limitada e, para algumas sementes, possivelmente um período muito curto.
precisa