English · Español
01 — IEEE-754, bit a bit¶
🇪🇸 Un fp32 son 32 bits divididos en signo (1), exponente (8) y mantisa (23). Aprender la fórmula
(-1)^s · 1.m · 2^(e-127)y memorizar los casos especiales (denormal, ±inf, NaN) es el trabajo de esta página. El resto de la fase consume estos hechos.
La disposición general¶
Un número en coma flotante binario IEEE-754 tiene tres campos:
Para los cuatro formatos que Borja verá en este currículo:
| Formato | Total | Signo | Exponente | Mantisa | Sesgo | Dígitos decimales |
|---|---|---|---|---|---|---|
| fp64 | 64 | 1 | 11 | 52 | 1023 | ~15.95 |
| fp32 | 32 | 1 | 8 | 23 | 127 | ~7.22 |
| fp16 | 16 | 1 | 5 | 10 | 15 | ~3.31 |
| bf16 | 16 | 1 | 8 | 7 | 127 | ~2.40 |
Fíjate en que bf16 y fp32 comparten el mismo ancho de exponente (8 bits) — bf16 es fp32 con la mantisa truncada. Eso hace que la conversión bf16 ↔ fp32 sea trivial (descartar o rellenar con ceros los 16 bits bajos) y es por lo que el hardware de aprendizaje automático (machine learning) lo adoptó.
La fórmula de decodificación¶
Para un número normal:
Donde:
- s es el bit de signo (0 = positivo, 1 = negativo).
- m es la mantisa interpretada como una fracción binaria 0.b_{22} b_{21} \dots b_0 (para fp32), dando un 1.m efectivo en [1, 2).
- e es la interpretación sin signo del campo de exponente, con bias = 127 para fp32 (1023 para fp64).
El 1. líder implícito es el bit oculto — no se almacena, pero cuenta. Esto da a fp32 un significand efectivo de 24 bits a partir de 23 bits almacenados.
Ejemplo resuelto — decodificar 0x3F800000¶
Bits: 0 01111111 00000000000000000000000 (reagrupados: signo / exponente 8 bits / mantisa 23 bits).
s = 0→ positivo.e = 0b01111111 = 127→ valor del exponente127 - 127 = 0.m = 0→ significand1.0.- Valor:
+1.0 × 2^0 = 1.0.
Ejemplo resuelto — decodificar 0x3ADA740E (= 1/600 ≈ 0.001\overline{6})¶
Este es el patrón de bits de la probabilidad uniforme del vocabulario de §A13. Calcúlalo:
- Bits:
0 01110101 10110100111010000001110. Reagrupados: signo0, exponente0b01110101 = 117, mantisa0b10110100111010000001110 = 5927950. - Exponente almacenado
117, exponente sin sesgar117 - 127 = -10. - Significand:
1 + 5927950 / 2^23 = 1 + 0.706666... = 1.706666... - Valor:
+1.706666... × 2^-10 = 1.706666... / 1024 ≈ 0.001\overline{6}.
El laboratorio 00-bit-anatomy.md te hace hacer esto a mano para 0.1, 1/600 y un número de un vector de logits de tiempos.
Rango y precisión de fp32¶
Rango de números normales:
- Mayor finito: exponente
0xFE = 254, mantisa todo unos. Valor ≈3.4028235 × 10^38. - Menor normal: exponente
0x01 = 1, mantisa cero. Valor2^{-126} ≈ 1.175 × 10^{-38}.
Épsilon de máquina: ε_fp32 = 2^{-23} ≈ 1.192 × 10^{-7}. Este es el menor x tal que 1 + x ≠ 1. El exponente no importa — la precisión es relativa, fijada por el ancho de mantisa.
El mismo épsilon de máquina implica: entre 1.0 y 2.0 hay 2^23 ≈ 8.4 × 10^6 valores fp32 representables. Entre 1024 y 2048 también hay 2^23 valores. Entre 1e30 y 2e30 siguen siendo 2^23 valores. El espaciado es ε × x en todas partes, así que la resolución cerca de cero es mucho más fina que cerca de 1e30.
Codificaciones especiales — las excepciones¶
IEEE-754 reserva dos valores de exponente para casos no normales:
Exponente todo ceros (e = 0) — cero y denormales¶
- Mantisa cero → cero con signo (
+0.0y-0.0existen ambos; comparan iguales pero sus bits difieren). - Mantisa no cero → denormal (también llamado "subnormal"). El bit líder implícito es
0(no1), y el exponente se trata como1 - bias(es decir,-126para fp32) en lugar de0 - bias. Esto da valores desde~1.4 × 10^{-45}(el menor fp32 positivo) hasta justo por debajo del menor normal~1.175 × 10^{-38}.
Los denormales pierden precisión gradualmente al acercarse a cero. Dan underflow gradual — sin ellos, cualquier cosa menor que el menor normal saltaría directamente a cero. Con ellos, la precisión se degrada suavemente hasta 1 bit.
Nota sobre rendimiento. Muchas CPUs manejan denormales vía microcódigo (~10-100× más lento que normales). Los kernels numéricos suelen activar el modo "flush-to-zero" para evitar la penalización. El entrenamiento de IA produce denormales rutinariamente cerca del final del entrenamiento cuando los pesos convergen; los proyectos conscientes hacen flush-to-zero deliberadamente.
Exponente todo unos (e = 0xFF para fp32, 0x7FF para fp64) — infinitos y NaN¶
- Mantisa cero →
±Inf. Resultado de overflow, o1 / 0, etc. - Mantisa no cero →
NaN(Not a Number). Resultado de0 / 0,Inf - Inf,sqrt(-1),log(-1), etc.
El patrón de mantisa de un NaN puede codificar un "payload" (que el estándar permite para uso diagnóstico). La mayoría del software simplemente propaga un patrón de bits NaN arbitrario. Dos NaN nunca son iguales — NaN == NaN es False. Esta es la forma IEEE-754-correcta de probar NaN: x != x.
La propagación de NaN es el asesino en aprendizaje automático (machine learning): cualquier operación que involucre un NaN produce un NaN. Un NaN en tu vector de longitud 600 de probabilidades verbales envenena el pase hacia delante entero, el gradiente entero, el paso entero.
Modos de redondeo¶
Cuando el resultado exacto de una operación no es representable, el resultado debe redondearse. IEEE-754 especifica cinco modos de redondeo; el predeterminado es round half to even ("redondeo del banquero"):
- Si el resultado exacto está más cerca de un valor representable, redondea allí.
- Si está exactamente a mitad de camino, redondea al que tenga el LSB de la mantisa en
0(es decir, al representable "par").
El redondeo del banquero elimina el pequeño sesgo estadístico que "round half up" introduce sobre muchas operaciones.
Otros modos (raramente usados en aprendizaje automático): redondear hacia cero (truncamiento), redondear hacia +∞, redondear hacia -∞, redondear la mitad alejándose de cero.
Por qué 0.1 + 0.2 ≠ 0.3¶
0.1 en binario es la fracción periódica infinita 0.0001100110011.... No se puede almacenar exactamente en fp32. El valor almacenado es el representable más cercano, desplazado en torno a 1.49 × 10^{-9} (llámalo 0.1 + ε₁).
0.2 análogamente es 0.2 + ε₂.
Su suma es 0.3 + ε₁ + ε₂ + ε_{round} donde ε_{round} es el redondeo de la suma, no de ninguno de los operandos. Esta suma no es el mismo valor fp32 que la representación más cercana de 0.3, que tiene su propio ε₃.
>>> hex(struct.unpack('<I', struct.pack('<f', 0.1 + 0.2))[0])
'0x3e99999a'
>>> hex(struct.unpack('<I', struct.pack('<f', 0.3))[0])
'0x3e99999a' # In fp32, they happen to match — but in fp64 they don't.
>>> 0.1 + 0.2 # Python uses fp64 by default
0.30000000000000004
La lección: nunca uses == con floats. Siempre abs(a - b) < tol, con una tolerancia que refleje el error acumulado esperado.
fp32 vs fp64 vs fp16 vs bf16 — cuándo tiene sentido cada uno¶
| Formato | Caso de uso | Por qué |
|---|---|---|
| fp64 | Cómputos de referencia / oráculo; chequeos de gradiente | Precisión máxima; lento en GPU; raramente usado en modelos desplegados |
| fp32 | Predeterminado para entrenamiento; línea base de seguridad | Amplio soporte de hardware; precisión suficiente para la mayoría de kernels |
| fp16 | Inferencia, a veces entrenamiento con cuidado | 2× menor, 2× más rápido en tensor cores; rango de exponente estrecho (desborda fácilmente) |
| bf16 | Default moderno de entrenamiento | Rango de exponente de fp32 con tamaño de fp16; mantisa precisión a la mitad pero raramente fatal |
El compromiso bf16 vs fp16: fp16 tiene más precisión de mantisa (10 bits vs 7), así que representa números cerca de 1.0 con más exactitud. Pero el exponente estrecho de 5 bits de fp16 (rango ~6 × 10^{-5} a ~6.5 × 10^4) desborda en el exp(x) de un softmax con x > 11 o así — mucho antes que el umbral de 89 de fp32. El exponente de 8 bits de bf16 coincide con fp32; sin problema de overflow. El entrenamiento moderno adoptó bf16 porque el rango de exponente importa más que la precisión de mantisa para gradientes y activaciones.
No escribirás código bf16 en la Fase 2 (no hay hardware), pero lo emularás en lab/00-bit-anatomy.md vía bit-cast y truncamiento.
Cómo ocurre un fp32 + fp32, mecánicamente¶
El camino del hardware (puedes implementarlo en Python en el lab 00):
- Alinear exponentes. La mantisa del operando menor se desplaza a la derecha hasta que los exponentes coinciden. Los bits desplazados se pierden (redondeados).
- Sumar (o restar) mantisas. Con un bit de guarda extra, un bit de redondeo y un bit pegajoso (los "GRS bits" de IEEE-754) para soportar el redondeo correcto.
- Normalizar. Desplazar el resultado para que el bit líder sea
1(o ponerlo a cero para denormales). - Redondear según el modo de redondeo actual.
Por esto (a + b) + c ≠ a + (b + c) en general: el paso 1 (alineación) descarta bits que dependen del orden de los operandos. Los bits descartados en un orden no son los mismos que en otro.
Lo que hace cada laboratorio de la Fase 2 con esto¶
- El laboratorio
00-bit-anatomy.mdte hace decodificar y re-codificar números incluyendo1/600,0.1y un logit de un vector de clasificación de tiempos. - El laboratorio
01-softmax-stability.mdte muestra qué pasa cuando un único exponente fp32 se hace lo bastante grande para empujarexp(x)más allá de3.4 × 10^{38}. - El laboratorio
02-summation-experiments.mdmide cómo una suma de longitud 10⁶ de probabilidades de magnitud~1/600acumula ε por suma. - El laboratorio
03-quantization-preview.mdmuestra qué se pierde cuando mapeas un array de logits fp32 a int8 y vuelta.
Recapitulación en un párrafo¶
Los floats IEEE-754 son (-1)^s · 1.m · 2^{e-bias} con excepciones para cero (e=0, m=0), denormales (e=0, m≠0), infinitos (e=max, m=0) y NaN (e=max, m≠0). fp32 da ~7 dígitos decimales de precisión y rango hasta ~3.4 × 10^{38}. bf16 cambia mantisa por rango de exponente. El redondeo es no asociativo, así que el orden de reducción importa. Memoriza la disposición — todas las fases posteriores la consumen.
Lo que esta página NO cubre¶
- Sutilezas específicas de hardware (legado Intel MMX, peculiaridades AVX-512). Fase 24.
- Coma flotante decimal (
decimal64). Fuera de alcance. - Cambios IEEE-754-2019 (operaciones aumentadas). Solo interés teórico.
- FP8 — cubierto aparte en
theory/04-precision-zoo.md.
Siguiente: theory/02-softmax-stability.md.