Pular para o conteúdo principal

Existe `Math.random()`, e depois existe `Math.random()`

· Leitura de 4 minutos
Yang Guo ([@hashseed](https://twitter.com/hashseed)), engenheiro de software e designer de dados

Math.random() retorna um valor do tipo Number com sinal positivo, maior ou igual a 0, mas menor que 1, escolhido aleatoriamente ou pseudoaleatoriamente com distribuição aproximadamente uniforme dentro desse intervalo, usando um algoritmo ou estratégia dependente da implementação. Esta função não aceita argumentos.

ES 2015, seção 20.2.2.27

Math.random() é a fonte de aleatoriedade mais conhecida e usada frequentemente em JavaScript. No V8 e na maioria dos outros motores JavaScript, é implementado usando um gerador de números pseudoaleatórios (PRNG). Como todos os PRNGs, o número aleatório é derivado de um estado interno, que é alterado por um algoritmo fixo para cada novo número aleatório. Portanto, para um estado inicial específico, a sequência de números aleatórios é determinística. Como o tamanho de bits n do estado interno é limitado, os números que um PRNG gera eventualmente se repetirão. O limite superior para o comprimento do período desse ciclo de permutação é 2n.

Existem muitos algoritmos PRNG diferentes; entre os mais conhecidos estão Mersenne-Twister e LCG. Cada um tem suas características particulares, vantagens e desvantagens. Idealmente, ele usaria o mínimo de memória possível para o estado inicial, seria rápido de executar, teria um grande comprimento de período e ofereceria uma distribuição aleatória de alta qualidade. Enquanto o uso de memória, desempenho e comprimento de período podem ser facilmente medidos ou calculados, a qualidade é mais difícil de determinar. Existe muita matemática por trás dos testes estatísticos para verificar a qualidade dos números aleatórios. O conjunto de testes padrão PRNG, TestU01, implementa muitos desses testes.

Até final de 2015 (até a versão 4.9.40), a escolha de PRNG do V8 era MWC1616 (multiplicar com transporte, combinando duas partes de 16 bits). Ele usa 64 bits de estado interno e se parece aproximadamente com isso:

uint32_t state0 = 1;
uint32_t state1 = 2;
uint32_t mwc1616() {
state0 = 18030 * (state0 & 0xFFFF) + (state0 >> 16);
state1 = 30903 * (state1 & 0xFFFF) + (state1 >> 16);
return state0 << 16 + (state1 & 0xFFFF);
}

O valor de 32 bits é então transformado em um número de ponto flutuante entre 0 e 1, de acordo com a especificação.

MWC1616 usa pouca memória e é bastante rápido para computar, mas infelizmente oferece qualidade inferior:

  • O número de valores aleatórios que pode gerar está limitado a 232, em oposição aos 252 números entre 0 e 1 que a ponto flutuante de precisão dupla pode representar.
  • A metade superior mais significativa do resultado depende quase totalmente do valor de state0. O comprimento do período seria no máximo 232, mas em vez de poucos ciclos de permutação grandes, existem muitos ciclos curtos. Com um estado inicial mal escolhido, o comprimento do ciclo pode ser inferior a 40 milhões.
  • Ele falha em muitos testes estatísticos no conjunto de testes TestU01.

Isso foi apontado para nós, e após entender o problema e realizar algumas pesquisas, decidimos reimplementar o Math.random com base em um algoritmo chamado xorshift128+. Ele usa 128 bits de estado interno, tem um comprimento de período de 2128 - 1 e passa em todos os testes do conjunto TestU01.

A implementação foi integrada no V8 v4.9.41.0 dentro de alguns dias após tomarmos conhecimento do problema. Tornou-se disponível com o Chrome 49. Tanto Firefox quanto Safari também mudaram para xorshift128+.

No V8 v7.1, a implementação foi ajustada novamente CL, confiando apenas em state0. Por favor, encontre mais detalhes sobre a implementação no código fonte.

No entanto, não se engane: embora o xorshift128+ seja uma grande melhoria em relação ao MWC1616, ele ainda não é criptograficamente seguro. Para casos de uso como hashing, geração de assinaturas e criptografia/descriptografia, PRNGs comuns são inadequados. A API de Criptografia da Web introduz window.crypto.getRandomValues, um método que retorna valores aleatórios criptograficamente seguros, com um custo de desempenho.

Por favor, lembre-se de que, se você encontrar áreas de melhoria no V8 e no Chrome, mesmo aquelas que — como esta — não afetam diretamente a conformidade com as especificações, estabilidade ou segurança, registre um problema em nosso rastreador de bugs.