Fundamentos · Tema 02

Números en una computadora

Cómo JavaScript representa cantidades reales mediante una colección finita de valores binarios y qué consecuencias tiene para nuestros algoritmos.

01 · Punto de partida

La computadora no almacena todos los números reales

Entre 0 y 1 existen infinitos números reales. Una computadora, en cambio, dispone de una cantidad finita de memoria. Por lo tanto, solo puede representar una colección finita de valores y debe aproximar todos los demás.

Esta limitación explica uno de los resultados más conocidos de JavaScript:

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);

La primera instrucción muestra 0.30000000000000004 y la segunda produce false. No es un error particular de JavaScript: los lenguajes que emplean punto flotante binario presentan el mismo fenómeno.

02 · Representación posicional

Del sistema decimal al sistema binario

El sistema decimal utiliza diez dígitos y potencias de 10. El número 325.4 puede descomponerse como:

3×10² + 2×10¹ + 5×10⁰ + 4×10⁻¹

El sistema binario utiliza únicamente los dígitos 0 y 1, y cada posición corresponde a una potencia de 2. Por ejemplo:

(101.101)₂ = 1×2² + 0×2¹ + 1×2⁰ + 1×2⁻¹ + 0×2⁻² + 1×2⁻³ = 5.625
Primeras potencias binarias a ambos lados del separador.
PotenciaValorUso
2³8Parte entera
2²4Parte entera
2⁰1Unidades
2⁻¹0.5Parte fraccionaria
2⁻²0.25Parte fraccionaria
2⁻³0.125Parte fraccionaria

Algunos números decimales tienen una expansión binaria finita. Por ejemplo, 0.5 = 0.1₂ y 0.25 = 0.01₂. Otros necesitan infinitos dígitos. El número decimal 0.1 comienza como 0.0001100110011…₂, con el patrón 0011 repitiéndose.

03 · Expansiones finitas e infinitas

El problema no es exclusivo del sistema binario

En decimal podemos representar 1/2 = 0.5 exactamente, pero no podemos escribir 1/3 con una cantidad finita de cifras: obtenemos 0.3333…. En binario ocurre algo equivalente con otras fracciones.

Representación finita

Denominador compatible con la base

1/8 = 0.125 en decimal y 0.001₂ en binario.

Representación periódica

Se necesitan infinitas cifras

1/10 = 0.1 en decimal, pero es periódico en binario.

Como la memoria es finita, la expansión binaria debe cortarse y redondearse. Cuando escribimos 0.1, JavaScript guarda el número binario representable más cercano. Al operar con varias aproximaciones, las pequeñas diferencias pueden hacerse visibles.

04 · Notación científica binaria

¿Qué significa punto flotante?

En notación científica decimal podemos escribir 6 250 000 como 6.25 × 10⁶. Separar una cantidad significativa de una potencia permite representar números muy grandes y muy pequeños con una cantidad fija de cifras.

El punto flotante aplica la misma idea en base 2:

valor = signo × significando × 2exponente

Se llama “flotante” porque el separador binario cambia de posición al variar el exponente. Esto ofrece un rango enorme, pero la precisión relativa es limitada: solo conservamos cierta cantidad de dígitos significativos.

Comparación conceptual de tres representaciones.
RepresentaciónVentajaLimitación
EnteroExacto dentro de su rangoNo representa fracciones
Punto fijoSeparación uniforme y control decimalRango limitado para una cantidad fija de bits
Punto flotanteGran rango dinámicoSeparación variable y redondeo

05 · El formato utilizado por JavaScript

IEEE 754 de doble precisión

El tipo Number de JavaScript utiliza, para los valores numéricos ordinarios, el formato binario de 64 bits conocido como binary64 o doble precisión del estándar IEEE 754.

1 bit de signo+11 bits de exponente+52 bits de fracción
Signo
Indica si el número es positivo o negativo.
Exponente
Determina la escala mediante una potencia de 2 y permite cubrir un rango muy amplio.
Fracción
Almacena los bits significativos. En números normales existe además un bit inicial implícito, por lo que la precisión efectiva es de 53 bits.

Esos 53 bits significativos equivalen aproximadamente a 15 o 16 cifras decimales confiables, según el valor. No significa que todo número tenga 16 decimales: son cifras significativas totales.

console.log(Number.EPSILON);
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MIN_SAFE_INTEGER);

06 · Constantes importantes

Rango, precisión y enteros seguros

Constantes numéricas útiles de JavaScript.
ConstanteSignificadoValor aproximado
Number.EPSILONDistancia entre 1 y el siguiente número representable2.220446049250313e-16
Number.MAX_VALUEMayor número finito positivo1.7976931348623157e+308
Number.MIN_VALUEMenor número positivo distinto de cero5e-324
Number.MAX_SAFE_INTEGERMayor entero consecutivo representable con seguridad9 007 199 254 740 991
Number.MIN_SAFE_INTEGERMenor entero consecutivo representable con seguridad−9 007 199 254 740 991

¿Qué significa “entero seguro”?

Un entero es seguro si puede representarse exactamente y las comparaciones entre enteros vecinos funcionan correctamente. Fuera del intervalo seguro, distintos enteros matemáticos pueden convertirse en el mismo Number.

const limite = Number.MAX_SAFE_INTEGER;

console.log(limite);
console.log(limite + 1);
console.log(limite + 2);
console.log(limite + 1 === limite + 2); // true
console.log(Number.isSafeInteger(limite + 1)); // false

07 · Una recta con huecos

La separación crece con la magnitud

Los valores de punto flotante no están distribuidos uniformemente sobre la recta real. Cerca de 1, la distancia al siguiente número es Number.EPSILON. Cerca de 2⁵², la distancia es 1; cerca de 2⁵³, es 2.

separación alrededor de 2e ≈ 2e−52

La precisión es aproximadamente constante en términos relativos, no absolutos. Podemos representar diferencias diminutas cerca de cero, pero esas mismas diferencias desaparecen cuando se suman a cantidades enormes.

console.log(1 + Number.EPSILON === 1);       // false
console.log(1e16 + 1 === 1e16);              // true
console.log(1e16 + 2 === 1e16);              // false

Este comportamiento será importante al calcular diferencias, acumular valores pequeños o elegir una tolerancia.

08 · Operaciones aproximadas

Redondeo y acumulación

El resultado exacto de una operación puede no pertenecer al conjunto de números representables. En ese caso se redondea a un valor disponible. Una secuencia larga de operaciones puede acumular estas pequeñas diferencias.

let total = 0;

for (let i = 0; i < 10; i++) {
  total += 0.1;
}

console.log(total);
console.log(total === 1);

Además, el orden de las operaciones puede cambiar el resultado porque la suma de punto flotante no es perfectamente asociativa:

const grande = 1e16;
const a = (grande + 1) - grande;
const b = grande + (1 - grande);

console.log(a); // 0
console.log(b); // 0 en este caso

const valores = [1e16, -1e16, 1];
console.log((valores[0] + valores[1]) + valores[2]); // 1
console.log(valores[0] + (valores[1] + valores[2])); // 0

Matemáticamente las dos asociaciones son equivalentes. Computacionalmente, la suma -1e16 + 1 pierde la unidad antes de sumar 1e16. Este fenómeno se denomina pérdida de significancia.

09 · Valores especiales

Infinity, NaN y cero con signo

IEEE 754 reserva patrones para describir resultados que no son números finitos ordinarios.

Valores especiales disponibles en JavaScript.
ValorEjemploComprobación recomendada
Infinity1 / 0 o desbordamiento positivoNumber.isFinite(x)
-Infinity-1 / 0Number.isFinite(x)
NaN0 / 0 o Math.sqrt(-1)Number.isNaN(x)
-0-1 / InfinityObject.is(x, -0)
console.log(Number.isFinite(10));       // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(0 / 0));       // true

console.log(NaN === NaN);               // false
console.log(Object.is(-0, 0));          // false
console.log(-0 === 0);                  // true

NaN significa “Not a Number”, pero su tipo en JavaScript es number. Se propaga por muchas operaciones y nunca es igual a sí mismo, por lo que debe detectarse con Number.isNaN.

10 · Comparaciones confiables

No comparar resultados aproximados solo con ===

Una comparación numérica suele aceptar los valores cuando su diferencia es menor que una tolerancia. Una tolerancia absoluta funciona cerca de cero:

|a − b| ≤ tolerancia

Pero una única tolerancia absoluta no se adapta a todas las escalas. Para cantidades grandes conviene combinar tolerancia absoluta y relativa:

|a − b| ≤ max(tolabs, tolrel × max(|a|, |b|))
function casiIguales(a, b, toleranciaAbsoluta = 1e-12,
                     toleranciaRelativa = 1e-9) {
  if (a === b) return true;
  if (!Number.isFinite(a) || !Number.isFinite(b)) return false;

  const diferencia = Math.abs(a - b);
  const escala = Math.max(Math.abs(a), Math.abs(b));

  return diferencia <= Math.max(
    toleranciaAbsoluta,
    toleranciaRelativa * escala
  );
}

console.log(casiIguales(0.1 + 0.2, 0.3));        // true
console.log(casiIguales(1_000_000.1, 1_000_000)); // false

11 · Enteros exactos grandes

Cuándo utilizar BigInt

BigInt representa enteros de tamaño arbitrario sin perder unidades. Se escribe agregando n al literal o usando la función BigInt.

const contador = 9_007_199_254_740_993n;
const siguiente = contador + 1n;

console.log(contador);
console.log(siguiente);
console.log(siguiente - contador); // 1n

BigInt es apropiado para contadores enormes, identificadores enteros y ciertos algoritmos criptográficos. No representa fracciones y no puede mezclarse directamente con Number en operaciones aritméticas.

Elección básica de representación numérica.
NecesidadOpción habitual
Cálculo científico, gráficos, simulaciónNumber
Enteros mayores que el intervalo seguroBigInt
Dinero con reglas decimales estrictasEnteros en la unidad mínima o biblioteca decimal
Precisión decimal arbitrariaBiblioteca especializada

Para dinero suele almacenarse la unidad mínima —por ejemplo, centavos— como entero, siempre que el rango sea suficiente. Redondear la salida con toFixed(2) solo cambia el formato del texto; no convierte internamente el cálculo en decimal exacto.

12 · Exploración interactiva

Laboratorio: la separación entre números representables

Elegí un exponente para construir x = 2ᵉ. El laboratorio obtiene el siguiente Number representable modificando directamente sus 64 bits y calcula la separación o ULP entre ambos.

EXPERIMENTO 02

Precisión según la magnitud

x = 2⁰ = 1

Los resultados numéricos aparecen debajo.
Valor x1
Siguiente representable1.0000000000000002
Separación ULP2.220446049250313e-16
x + ULP/21

Cerca de 1, la separación coincide con Number.EPSILON.

Preguntas para explorar

  1. ¿Qué separación observás en 2⁰, 2¹⁰ y 2²⁰?
  2. Mové el control hasta e = 52. ¿Qué sucede con las fracciones?
  3. En e = 53, ¿cuál es el siguiente entero representable?
  4. ¿Por qué sumar la mitad de la separación puede devolver el mismo valor?

13 · Programación defensiva

Lista de comprobación

  • Validá las entradas con Number.isFinite cuando el algoritmo requiere números finitos.
  • Usá Number.isSafeInteger para identificadores y contadores que deben ser enteros exactos.
  • Compará aproximaciones con tolerancias justificadas por el problema.
  • Evitá restar números casi iguales cuando exista una formulación algebraica más estable.
  • Sumá primero las cantidades pequeñas cuando debas acumular valores con escalas muy diferentes.
  • No confundas formato de salida con precisión interna.
  • Registrá advertencias si una operación produce NaN o un valor no finito.

14 · Comprensión

Confusiones frecuentes

«JavaScript tiene un error porque 0.1 + 0.2 no es exactamente 0.3»

El comportamiento surge de aproximar esas fracciones en base 2 y está presente en la aritmética binaria IEEE 754 de muchos lenguajes.

«Number.EPSILON es el menor número positivo»

Number.EPSILON es la separación entre 1 y su sucesor. El menor positivo distinto de cero es Number.MIN_VALUE.

«toFixed aumenta la precisión del cálculo»

toFixed produce una cadena formateada y redondeada. No recupera información perdida ni cambia la representación del valor original.

«Todos los enteros son exactos porque no tienen decimales»

Solo los enteros del intervalo seguro mantienen representación y comparaciones confiables de forma consecutiva.

«Una tolerancia de Number.EPSILON sirve para todo»

La separación crece con la magnitud y los problemas tienen requisitos distintos. Una tolerancia debe contemplar escala y error aceptable.

15 · Práctica guiada

Ejercicios

Ejercicio 1: convertir una fracción binaria

Convertí (1101.011)₂ a decimal.

Ver solución

1×2³ + 1×2² + 0×2¹ + 1×2⁰ + 0×2⁻¹ + 1×2⁻² + 1×2⁻³ = 8 + 4 + 1 + 0.25 + 0.125 = 13.375.

Ejercicio 2: explicar una comparación

Explicá por qué 0.1 + 0.2 === 0.3 produce false y escribí una comparación apropiada.

Ver solución

Las tres fracciones se aproximan por separado en binario. La suma almacenada y el valor almacenado para 0.3 difieren ligeramente. Podemos comparar con una tolerancia adecuada:

const diferencia = Math.abs((0.1 + 0.2) - 0.3);
console.log(diferencia <= 1e-12); // true

Ejercicio 3: detectar entradas inválidas

Escribí una función promedio que acepte un arreglo no vacío de números finitos y rechace cualquier otro dato.

Ver una posible solución
function promedio(valores) {
  if (!Array.isArray(valores) || valores.length === 0) {
    throw new Error("Se requiere un arreglo no vacío");
  }
  if (!valores.every(Number.isFinite)) {
    throw new Error("Todos los valores deben ser finitos");
  }

  return valores.reduce((suma, valor) => suma + valor, 0)
    / valores.length;
}

console.log(promedio([2, 4, 6]));

Ejercicio 4: elegir una representación

Indicá una representación conveniente para cada caso: una simulación física, un contador que puede superar 2⁵³, precios con centavos y el resultado de una función trigonométrica.

Ver respuesta razonada
  • Simulación física: Number, con control del error numérico.
  • Contador mayor que 2⁵³: BigInt.
  • Precios: enteros en centavos si las reglas lo permiten, o una biblioteca decimal.
  • Función trigonométrica: Number, porque las funciones de Math trabajan con punto flotante.

Ejercicio 5: investigar el orden de suma

Ejecutá el siguiente experimento y explicá la diferencia:

const datos = [1e16, 1, -1e16];

const izquierda = (datos[0] + datos[1]) + datos[2];
const alternativa = (datos[0] + datos[2]) + datos[1];

console.log(izquierda);
console.log(alternativa);
Ver explicación

En la primera expresión, sumar 1 a 1e16 no cambia el valor representado; después la resta produce 0. En la segunda, los números grandes se cancelan primero y la unidad se conserva, por lo que el resultado es 1.

16 · Síntesis

Ideas para recordar

  • Number utiliza principalmente punto flotante binario IEEE 754 de 64 bits.
  • Muchos decimales sencillos no tienen una representación binaria finita.
  • La doble precisión dispone de 53 bits significativos, unas 15 o 16 cifras decimales.
  • La separación entre números representables aumenta con la magnitud.
  • Los enteros solo son consecutivos y seguros hasta Number.MAX_SAFE_INTEGER.
  • NaN, los infinitos y -0 requieren comprobaciones específicas.
  • Las comparaciones aproximadas deben combinar tolerancias absolutas y relativas.
  • BigInt permite enteros grandes exactos, pero no fracciones.

En el próximo tema aprenderemos a medir formalmente el error mediante error absoluto, relativo y porcentual, y veremos cuándo resulta apropiado utilizar cada uno.