Números Pseudoaleatorios · Tema 21

Generación en intervalos arbitrarios

De U(0,1) a [a,b): la fórmula a + u·(b−a), sus bordes heredados y los tres errores que tuercen la escala.

01 · Punto de partida

Del unitario al mundo real

El Tema 20 te dejó un u impecable en [0,1). Pero nadie simula “unidades abstractas”: simulás temperaturas en [−10,40), esperas en [0,30) o factores en [0.8,1.2). El paso es un cambio de escala lineal, y aunque parece trivial, concentra los bugs más comunes del curso: olvidar restar a, incluir el b o truncar con int.

La buena noticia: una sola fórmula bien aplicada hereda gratis la uniformidad, los bordes y la resolución del [0,1).

  • ¿Por qué a + u·(b−a) y no a + u·b?
  • ¿El intervalo resultado incluye a b?
  • ¿Qué media y qué P(x < t) debe dar un [a,b) parejo?
  • ¿Cuándo conviene uniform(a,b) y cuándo hacerlo a mano?

02 · Definición

Estirar y desplazar

◈

Longitud

L = b−a es el estirón: multiplica la densidad 1 → 1/L y el paso de resolución 1/m → L/m.

⇄

Desplazamiento

Sumar a mueve la grilla sin deformarla: u=0 → a, el 1 inalcanzable → b inalcanzable.

▣

Bordes [a,b)

Heredados: a sale con prob. 1/m, b nunca. Así x < b siempre y x >= a siempre.

Fórmula frente a atajos rotos.
RecetaRango realVeredicto
a + u*(b−a)[a,b) exactoCanónica: usar siempre
a + u*b[a, a+b)Olvidó restar a: desplaza mal si a≠0
u*(b−a) sin +a[0, b−a)Solo vale si a=0

03 · Casos y límites

Negativos, grandes y flotante

La linealidad no distingue signos ni magnitudes, pero el flotante sí: con b−a enorme la resolución L/m puede superar tu tolerancia, y con a grande y L chico la suma pierde colas (cancelación del Tema 42).

a > b
Intervalo invertido: validar a < b con assert antes de generar. Sin chequeo, la “uniforme” sale decreciente y los tests de rango fallan.
a == b
Longitud cero: todo valor es a. No es azar: devolver constante documentada o lanzar error según tu API.
Resolución L/m
Con m = 2³² y L = 50 hay 4e9 puntos separados 1,2e−8: continuo a ojo. Con LCG m = 16 y L = 50 el paso es 3,125: dentado real.

Bien escalado

u canónico × L + a

Media ≈ (a+b)/2, mínimo ≥ a, máximo < b. P(x < t) lineal: la firma de la uniformidad continua (Tema 27).

Mal escalado

Truncar o redondear

int(a+u·L) o round() convierten el continuo en discreto con masas desparejas: eso pertenece al Tema 22, no aquí.

04 · Ejemplos

El mismo u, tres mundos

u en [0,1)→× (b−a)→+ a
  1. 1
    Espera [0,30) min.

    x = 0 + u·30. Media 15; P(x < 5) = 1/6. El 30 nunca sale: la espera “exacta” no existe.

  2. 2
    Temperatura [−10,40).

    x = −10 + u·50. El signo no cambia nada: estirón 50 y desplazamiento −10.

  3. 3
    Factor [0.8,1.2).

    x = 0.8 + u·0.4. L chico: la resolución del generador sobra y el flotante es exacto.

05 · Implementación en Python

Escalar y auditar

Una función de tres líneas más un auditor de contrato: rango, media y probabilidad en un umbral. Si el auditor pasa, la escala es sana.

Python en tu navegador. Cambiá a, b a negativos o invertidos y mirá qué denuncia el auditor.

import random

def uniforme_ab(a, b, n=5000, semilla=7):
    assert a < b, "se exige a < b"
    rng = random.Random(semilla)
    return [a + rng.random() * (b - a) for _ in range(n)]


xs = uniforme_ab(-10, 40, n=5000)
print("min:", round(min(xs), 4), "max:", round(max(xs), 4))
print("media:", round(sum(xs) / len(xs), 4), "esperada:", 15.0)
print("P(x < 0):", round(sum(1 for x in xs if x < 0) / len(xs), 4), "esperada:", 0.2)

Mínimo ≥ −10, máximo < 40, media ≈ 15 y P(x<0) ≈ 0,2: la firma lineal de [−10,40).

El bug de olvidar la resta

import random

rng = random.Random(7)
mal = [-10 + rng.random() * 40 for _ in range(3)]
rng = random.Random(7)
bien = [-10 + rng.random() * 50 for _ in range(3)]
print("mal :", [round(v, 4) for v in mal])
print("bien:", [round(v, 4) for v in bien])

06 · Exploración

Laboratorio: estirar el [0,1)

Mové los extremos a y b y la cantidad N. La banda verde es tu [a,b); la línea naranja es el b inalcanzable. El panel compara media observada con (a+b)/2 y cuenta fugas.

EXPERIMENTO 21

x = a + u·(b−a)

media → (a+b)/2

Los resultados numéricos aparecen debajo.
Media obs.—
Media esp.—
Máximo—
Fugas (≥b o <a)—

Con [−10,40) la media tiende a 15.

Si a ≥ b el laboratorio lo marca como intervalo inválido: la fórmula exige a < b.

Preguntas para explorar

  1. Con N = 20 y [−10,40): ¿la media observada está cerca de 15? ¿Y con N = 400?
  2. Poné a = 30, b = 40: ¿cambia la forma o solo la posición? ¿Qué pasa con a = 40, b = −10?
  3. ¿Algún valor toca o supera a b? ¿Por qué eso confirma el [a,b)?
Ver respuestas sugeridas
  1. Con N chico la media oscila (±4 es normal); con N grande se pega a 15. Igual que la moneda del Tema 1: dilución.
  2. Solo la posición y la longitud: la uniformidad es invariante por escala. Con a ≥ b el intervalo es inválido y el lab lo denuncia.
  3. Nunca: u < 1 implica x < b siempre. Un solo x ≥ b delataría un u fugado del Tema 20.

07 · Comprensión

Confusiones frecuentes

«Para [−10,40) hago −10 + u·40»

Da [−10,30): olvidaste que L = 40−(−10) = 50. El error se esconde si a = 0 y explota con negativos.

«El máximo puede ser b, lo incluyo»

No: b es inalcanzable por herencia del 1. Forzarlo con min(x, b) crea un átomo en b detectable en el Tema 27.

«Uso int() para quedarme en el rango»

int() trunca hacia cero y rompe negativos y bordes: para discretos usá la receta del Tema 22, no truncamiento.

«Escalar cambia la calidad del generador»

Una transformación lineal no crea ni destruye uniformidad: si el u era bueno, el x es bueno; si el u estaba sesgado, la escala lo hereda.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: espera de colectivo

Simulá 3000 esperas en [0,30) y estimá P(espera < 5). Compará con el valor teórico 1/6.

import random

rng = random.Random(11)
xs = [0 + rng.random() * 30 for _ in range(3000)]
print(round(sum(1 for x in xs if x < 5) / len(xs), 4))
Ver solución razonada

≈ 0,1667 ± 0,015. Si te da ≈ 0,13 o 0,20 repetidamente, revisá la fórmula: probablemente usaste mal L.

Ejercicio 2: termómetro con negativos

Generá 5 temperaturas en [−10,40) con semilla fija y verificá bordes: todas ≥ −10 y < 40.

import random

rng = random.Random(3)
ts = [-10 + rng.random() * 50 for _ in range(5)]
print([round(t, 4) for t in ts])
print(all(-10 <= t < 40 for t in ts))
Ver solución

Cinco valores entre −10 y 40 con True. Con la fórmula rota −10 + u·40 el máximo posible sería 30: la mitad del rango perdida.

Ejercicio 3: auditor reutilizable

Escribí auditar(xs, a, b) que devuelva min, max, media y fugas, y probalo con [0.8,1.2).

Ver una posible respuesta
import random

def auditar(xs, a, b):
    fugas = sum(1 for x in xs if not (a <= x < b))
    return min(xs), max(xs), sum(xs) / len(xs), fugas


rng = random.Random(9)
xs = [0.8 + rng.random() * 0.4 for _ in range(2000)]
print(auditar(xs, 0.8, 1.2))

Fugas 0, media ≈ 1,0 y max < 1,2. Reutilizá este auditor en los Temas 22–25 antes de culpar al generador.

09 · Síntesis

Ideas para recordar

  • Continuo en [a,b): x = a + u·(b−a), media (a+b)/2.
  • L = b−a manda: densidad 1/L y resolución L/m.
  • Bordes heredados: a sí sale, b nunca; exigir a < b.
  • No truncar ni redondear continuos: los discretos van por el Tema 22.
  • La escala no mejora ni empeora al generador: hereda su calidad.

En el próximo tema cruzamos a lo discreto: generación de enteros aleatorios en rangos sin trampas de módulo.