Números aleatórios em JavaScript: do caos à ordem
\nImagine: você está programando um jogo onde precisa escolher aleatoriamente um baú de tesouro de 1 a 100. Ou, talvez, criando um teste A/B que seleciona uma variante aleatória para o usuário. Parece simples, não é? Mas o diabo, como sempre, está nos detalhes. A questão de gerar números inteiros aleatórios em JavaScript em um intervalo específico é uma das mais populares no Stack Overflow, e não é à toa. Até desenvolvedores experientes às vezes tropeçam nas armadilhas do Math.random().
Neste artigo, vamos analisar não apenas os métodos clássicos, mas também as melhores práticas de 2025: como tornar o código não apenas funcional, mas também seguro, previsível (no bom sentido) e eficiente. Vamos nessa!
\n\nO clássico: Math.random() e suas armadilhas
\nVamos começar pelo básico. A função embutida Math.random() retorna um número pseudoaleatório de ponto flutuante no intervalo [0, 1). Ponto-chave: 0 está incluído, mas 1 não. Esta é uma distribuição "injusta" se você deseja obter números inteiros.
Aqui está a maneira mais comum (e mais incorreta) que frequentemente aparece em livros antigos:
\n// EXEMPLO RUIM: viés de probabilidade\nfunction getRandomBad(min, max) {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nÀ primeira vista, parece correto. Mas vamos mais fundo. Math.floor() arredonda para baixo. Se Math.random() gerar um número muito próximo de 1 (mas não igual), a multiplicação por (max - min + 1) resultará em um valor menor que max + 1, e Math.floor() o truncará. No entanto, devido à precisão limitada dos números de ponto flutuante (IEEE 754), alguns valores aparecem com menos frequência que outros. Para um intervalo de 0 a 1, isso é imperceptível, mas para intervalos grandes (por exemplo, de 1 a 1.000.000), o viés torna-se estatisticamente significativo.
Conclusão: esta fórmula funciona para intervalos pequenos, mas não é "criptograficamente pura" e pode causar distorção na distribuição.
\n\nA fórmula correta da MDN
\nA Mozilla Developer Network (MDN) recomenda uma abordagem mais precisa que minimiza erros de arredondamento:
\n// Maneira boa e universal\nfunction getRandomIntInclusive(min, max) {\n const minCeiled = Math.ceil(min);\n const maxFloored = Math.floor(max);\n return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled);\n}\n\nA diferença é que explicitamente convertemos os limites para números inteiros usando Math.ceil() e Math.floor(). Isso garante que min e max sejam processados corretamente, mesmo se você acidentalmente passar um número fracionário. Use este método como padrão básico em 2025.
Alternativas: quando Math.random() não é suficiente
\nO clássico Math.random() é um gerador pseudoaleatório (PRNG). Ele é determinístico: se você conhecer a semente inicial (seed), pode prever toda a sequência. Para a maioria das aplicações web (animações, conteúdo aleatório), isso é suficiente. Mas existem cenários onde é necessária aleatoriedade real:
- \n
- Segurança: geração de tokens, senhas, CSRF tokens. \n
- Criptografia: criptografia de dados no cliente. \n
- Jogos de azar: distribuição justa de prêmios. \n
Nestes casos, o crypto.getRandomValues() vem em socorro — um gerador criptograficamente seguro (CSPRNG). Ele usa fontes de entropia do sistema e é muito menos previsível.
Geração de números inteiros com crypto.getRandomValues()
\nAqui está como fica uma implementação moderna em 2025:
\n// Geração segura de um número inteiro aleatório no intervalo [min, max]\nfunction getSecureRandomInt(min, max) {\n const range = max - min + 1;\n // Valor máximo múltiplo de range (para evitar viés