Skip to content

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:

| sign | exponent | mantissa (significand) |
|  1   |    E     |           M            |

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:

\[ x = (-1)^s \cdot 1.m \cdot 2^{e - \text{bias}} \]

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 exponente 127 - 127 = 0.
  • m = 0 → significand 1.0.
  • Valor: +1.0 × 2^0 = 1.0.

Ejemplo resuelto — decodificar 0x3ADA740E (= 1/6000.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: signo 0, exponente 0b01110101 = 117, mantisa 0b10110100111010000001110 = 5927950.
  • Exponente almacenado 117, exponente sin sesgar 117 - 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. Valor 2^{-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.0 y -0.0 existen ambos; comparan iguales pero sus bits difieren).
  • Mantisa no cerodenormal (también llamado "subnormal"). El bit líder implícito es 0 (no 1), y el exponente se trata como 1 - bias (es decir, -126 para fp32) en lugar de 0 - 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, o 1 / 0, etc.
  • Mantisa no ceroNaN (Not a Number). Resultado de 0 / 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 igualesNaN == 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):

  1. Alinear exponentes. La mantisa del operando menor se desplaza a la derecha hasta que los exponentes coinciden. Los bits desplazados se pierden (redondeados).
  2. 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.
  3. Normalizar. Desplazar el resultado para que el bit líder sea 1 (o ponerlo a cero para denormales).
  4. 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.md te hace decodificar y re-codificar números incluyendo 1/600, 0.1 y un logit de un vector de clasificación de tiempos.
  • El laboratorio 01-softmax-stability.md te muestra qué pasa cuando un único exponente fp32 se hace lo bastante grande para empujar exp(x) más allá de 3.4 × 10^{38}.
  • El laboratorio 02-summation-experiments.md mide cómo una suma de longitud 10⁶ de probabilidades de magnitud ~1/600 acumula ε por suma.
  • El laboratorio 03-quantization-preview.md muestra 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.