01 · Punto de partida
El generador escupe enteros, vos querés decimales
Todo generador de los Temas 12 a 18 entrega enteros: un LCG da 0 … m−1, un xorshift32 da 0 … 2³²−1. Pero la simulación habla en probabilidades, proporciones y si u < p: necesita decimales en [0,1) con densidad pareja. El puente es una división, y si la hacés mal introducís el primer sesgo de tu simulación.
Este tema cierra esa brecha: la receta canónica u = x / m, qué valores puede tomar realmente y por qué el 1 queda afuera.
- ¿Por qué se divide por m y no por m−1?
- ¿Cuántos decimales distintos puede dar un generador con módulo m?
- ¿Puede salir exactamente 0 o exactamente 1?
- ¿Por qué un double necesita 53 bits y no 32?
02 · Definición
U(0,1): pareja y sin bordes tramposos
Normalizar
u = x / m en flotante. Cada entero tiene chance 1/m: la uniformidad discreta se hereda al decimal.
Resolución
Con módulo m solo existen m decimales, separados 1/m. Con m=16 hay “escalones” de 0,0625; con 2³² son invisibles.
Bordes [0,1)
Cerrado en 0, abierto en 1: x=0 → u=0 posible, u=1 imposible. Así int(u*k) da 0 … k−1 parejo.
| Receta | Rango real | Veredicto |
|---|---|---|
u = x / m | [0,1): 0 sí, 1 nunca | Canónica: usar siempre |
u = x / (m−1) | [0,1]: el 1 sale con prob. 1/m | Rota int(u*k) y log(u) |
u = (x+0.5) / m | Nunca da 0 ni se pega al borde | Solo si un método exige (0,1) abierto |
03 · Precisión real
32 bits no llenan un double
Un float64 guarda 53 bits de mantisa: puede distinguir múltiplos de 2⁻⁵³ ≈ 1,1e−16. Un solo uint32 solo distingue múltiplos de 2⁻³² ≈ 2,3e−10: deja 2²¹ “huecos” vacíos entre cada par de valores. Por eso las librerías serias combinan dos enteros de 32 bits en uno de 53.
- 53 bits (CPython / NumPy)
u = (a·2²⁷ + b·2⁻²⁶…) / 2⁵³: conade 27 bits ybde 26 bits se cubre toda la mantisa del double.- División entera accidental
- En Python 2 o en C con enteros,
x / mtrunca a 0. Forzar flotante:x / float(m)ox / men Python 3. - El 0 exacto
u = 0sale con prob. 1/m: métodos como-log(u)deben mapearlo a (0,1] con1−u(Tema 20).
Bien normalizado
u = x / m en float
m valores parejos en [0,1), media ≈ 0,5 y P(u < p) = p salvo resolución. Base de todo lo que sigue.
Mal normalizado
Dividir por m−1
El 1 aparece 1/m de las veces: int(u*6) favorece al 6 y u==1 rompe logaritmos y divisiones.
04 · Ejemplos
Del módulo al decimal
- 1LCG m = 16.
Solo 16 decimales: 0, 0,0625, 0,125, …, 0,9375. Ideal para ver los “escalones” en el laboratorio.
- 2xorshift32 / MT.
u = x / 2**32: 4294967296 puntos, escalón 2,3e−10. A ojo, continuo. - 3Double de 53 bits.
CPython combina 27 + 26 bits: escalón 1,1e−16, toda la mantisa cubierta sin huecos.
05 · Implementación en Python
Normalizar sin sesgo
Tomamos el LCG didáctico (a=5, c=3, m=16) y lo convertimos: mirá cómo los 16 valores se repiten cíclicamente y nunca tocan el 1.
Python en tu navegador. Cambiá el divisor a m−1 y observá cómo aparece el 1 y se rompe el rango.
def lcg_uniformes(semilla, n=16, a=5, c=3, m=16):
x = semilla % m
sal = []
for _ in range(n):
x = (a * x + c) % m
sal.append(x / m)
return sal
us = lcg_uniformes(7)
print([round(u, 4) for u in us])
print("min:", min(us), "max:", max(us), "distintos:", len(set(us)))
16 valores distintos como máximo, entre 0 y 0,9375. El 1 no aparece jamás: eso es [0,1).
53 bits como CPython
import random
# un uint32 solo llena 32 de los 53 bits del double
x32 = random.getrandbits(32) / 2**32
# receta real de CPython: 27 + 26 bits
a = random.getrandbits(27)
b = random.getrandbits(26)
u53 = (a * 67108864 + b) / 9007199254740992
print(round(x32, 12), round(u53, 12))
print("escalon 32 bits:", 2**-32, "escalon 53 bits:", 2**-53)
06 · Exploración
Laboratorio: la resolución se ve
Elegí el módulo del LCG testigo y la cantidad de valores. La curva es u(n); con m chico verás bandas discretas (solo m alturas posibles). El panel mide resolución 1/m, distintos y si aparece el 1.
Escalones de 1/m
u = x / m en [0,1)
Con m = 16 solo hay 16 alturas posibles.
Con m = 16 la cuadrícula es evidente; con m = 65536 parece continua, pero sigue siendo discreta con paso 1/65536.
Preguntas para explorar
- Con m = 16 y N = 240: ¿cuántos distintos hay como máximo? ¿Qué pasa al pedir más valores que el módulo?
- ¿Alguna configuración llega a u = 1? ¿Y a u = 0?
- Si tu regla es “aprobar con u < 0,001”, ¿qué módulos la hacen imposible o dentada?
Ver respuestas sugeridas
- 16 como máximo: pasado el período se repiten (Tema 10). Pedir 240 es mirar el mismo ciclo 15 veces.
- El 1 nunca; el 0 sí cuando
x = 0. Dividir porm−1“crearía” un 1 falso con prob. 1/m. - Con m = 16 el umbral 0,001 cae dentro del primer escalón: o nunca ocurre o sale con prob. 1/16, nunca 1/1000. Necesitás m » 1000.
07 · Comprensión
Confusiones frecuentes
«Divido por m−1 para aprovechar hasta el 1»
El 1 no debe salir: rompe int(u*k), u**p con p chico y todo log(u). El rango correcto es [0,1), no [0,1].
«En C me da siempre 0»
División entera: x / m con dos enteros trunca. Convertí primero: (double)x / m; en Python 3 ya es flotante.
«Imprimo 16 decimales, tengo precisión 1e−16»
Imprimir no crea resolución: con m = 2³² el paso real es 2,3e−10 aunque imprimas 16 cifras. Los ceros del final son relleno.
«El 0 nunca sale, lo ignoro»
Sale con prob. 1/m y rompe -log(u)/λ con división por cero o infinito. Se trata en el Tema 20 con 1−u.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: el rango prometido
Generá 2000 uniformes del LCG m = 16 normalizado y verificá que están en [0,1): mínimo ≥ 0, máximo < 1.
def lcg_u(semilla, n, a=5, c=3, m=16):
x = semilla % m
for _ in range(n):
x = (a * x + c) % m
yield x / m
us = list(lcg_u(7, 2000))
print(min(us), max(us), all(0 <= u < 1 for u in us))
Ver solución razonada
Mínimo 0,0 y máximo 0,9375 con True: el máximo teórico es (m−1)/m. Si dividieras por m−1 verías un 1,0 intruso.
Ejercicio 2: contar la resolución
¿Cuántos valores distintos puede dar x / 256? Comprobalo generando 5000 y contando el conjunto.
def lcg_u256(semilla, n):
x = semilla % 256
for _ in range(n):
x = (1103515245 * x + 12345) % 256
yield x / 256
print(len(set(lcg_u256(1, 5000))))
Ver solución
256 como máximo (menos si el período es menor). La resolución es 1/256 ≈ 0,0039: dos umbrales más cercanos que eso son indistinguibles.
Ejercicio 3: el 0 que rompe el log
Forzá u = 0 con semilla adecuada y mostrá que 1−u lo rescata para -log(u).
Ver una posible respuesta
import math
# con m=16, semilla 5 da x=0 en el primer paso: u=0
x = (5 * 5 + 3) % 16
u = x / 16
print("u:", u)
print("1-u:", 1 - u, "-log(1-u):", round(-math.log(1 - u), 6))
u = 0 existe y log(0) explota; 1−u = 1 sigue siendo uniforme en (0,1] y mantiene la distribución. Detalle completo en el Tema 20.
09 · Síntesis
Ideas para recordar
- Normalizar es
u = x / men flotante: m valores en [0,1). - Resolución
1/m: el 0 sale, el 1 nunca; no imprimir crea precisión. - Un uint32 no llena un double: hacen falta 53 bits (27 + 26).
- Dividir por
m−1o truncar con enteros son los dos sesgos clásicos. - Todo lo que sigue (intervalos, enteros, sorteos) cuelga de este u.
En el próximo tema haremos zoom en el borde: la transformación al intervalo [0,1), el 0 exacto y el mapeo a (0,1].