Cómo funcionan de verdad los generadores de aleatorios
Casi todo el código de números aleatorios tiene un sesgo sutil. Aquí dónde, y por qué casi nunca importa.
Pseudoaleatorio frente a criptográfico
Un PRNG es un algoritmo determinista con estado interno. Con la misma semilla produce la misma secuencia: es una ventaja para simular y un defecto fatal para contraseñas. Math.random() es uno: se siembra una vez por página y es predecible si observas suficiente salida.
Un CSPRNG toma del depósito de entropía del sistema operativo y está diseñado para que observar la salida pasada no permita predecir la futura. Los navegadores lo exponen como crypto.getRandomValues().
El problema del sesgo de módulo
El atajo habitual es randomUint32() % n. Parece correcto, pero 2^32 no es divisible por la mayoría de los valores de n. Si n es 3, los valores 0, 1 y 2 reciben 1.431.655.765, 1.431.655.765 y 1.431.655.766 oportunidades: un sesgo mínimo hacia el 2 que no desaparece por muchas muestras que se tomen.
La solución es el rejection sampling: calcular el mayor múltiplo de n que cabe en 2^32, descartar lo que lo supere y solo entonces aplicar el resto. Cuesta una extracción ocasional y elimina el sesgo por completo.
La coma flotante lo empeora
Math.floor(Math.random() * n) tiene el mismo tipo de problema con n grandes, además de que un double tiene 53 bits de mantisa: por encima de 2^53 hay enteros que no se pueden representar, así que un rango mayor pierde valores en silencio.
Para decimales, lo fiable es escalar a enteros, extraer y dividir de vuelta. Trabajar en coma flotante de principio a fin introduce redondeos que pueden sacar el resultado del rango pedido.
Por qué la uniformidad importa en un sorteo
Un sesgo del 0,00000002 % es irrelevante para un dado y sigue siendo irrelevante para una rifa. Pero la misma función sesgada usada para generar tokens de sesión o claves de cifrado es otra cosa: el sesgo es justo el tipo de estructura que un atacante aprovecha para reducir un espacio de búsqueda.
La regla práctica: Math.random() para efectos visuales, y un CSPRNG con rejection sampling para cualquier cosa que a alguien le reporte ventaja predecir.
Preguntas frecuentes
¿Basta Math.random() para elegir al azar?
Para elegir restaurante, sí. Para un sorteo con premio, usa una fuente criptográfica, de modo que el resultado no se pueda predecir ni reproducir por quien esté viendo la página.
¿Qué es el sesgo de módulo en una frase?
Cuando el rango de enteros pedido no divide exactamente el rango de salida del generador, algunos resultados se vuelven ligeramente más probables que otros.
¿Puede un navegador generar aleatoriedad verdadera?
No. La aleatoriedad verdadera necesita una fuente física. El sistema recoge entropía de eventos de hardware y siembra el CSPRNG, que es el estándar práctico en seguridad.