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:
El sistema binario utiliza únicamente los dígitos 0 y 1, y cada posición corresponde a una potencia de 2. Por ejemplo:
| Potencia | Valor | Uso |
|---|---|---|
2³ | 8 | Parte entera |
2² | 4 | Parte entera |
2⁰ | 1 | Unidades |
2⁻¹ | 0.5 | Parte fraccionaria |
2⁻² | 0.25 | Parte fraccionaria |
2⁻³ | 0.125 | Parte 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:
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.
| Representación | Ventaja | Limitación |
|---|---|---|
| Entero | Exacto dentro de su rango | No representa fracciones |
| Punto fijo | Separación uniforme y control decimal | Rango limitado para una cantidad fija de bits |
| Punto flotante | Gran rango dinámico | Separació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.
- 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
| Constante | Significado | Valor aproximado |
|---|---|---|
Number.EPSILON | Distancia entre 1 y el siguiente número representable | 2.220446049250313e-16 |
Number.MAX_VALUE | Mayor número finito positivo | 1.7976931348623157e+308 |
Number.MIN_VALUE | Menor número positivo distinto de cero | 5e-324 |
Number.MAX_SAFE_INTEGER | Mayor entero consecutivo representable con seguridad | 9 007 199 254 740 991 |
Number.MIN_SAFE_INTEGER | Menor 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.
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.
| Valor | Ejemplo | Comprobación recomendada |
|---|---|---|
Infinity | 1 / 0 o desbordamiento positivo | Number.isFinite(x) |
-Infinity | -1 / 0 | Number.isFinite(x) |
NaN | 0 / 0 o Math.sqrt(-1) | Number.isNaN(x) |
-0 | -1 / Infinity | Object.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:
Pero una única tolerancia absoluta no se adapta a todas las escalas. Para cantidades grandes conviene combinar tolerancia absoluta y relativa:
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.
| Necesidad | Opción habitual |
|---|---|
| Cálculo científico, gráficos, simulación | Number |
| Enteros mayores que el intervalo seguro | BigInt |
| Dinero con reglas decimales estrictas | Enteros en la unidad mínima o biblioteca decimal |
| Precisión decimal arbitraria | Biblioteca 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.
Precisión según la magnitud
x = 2⁰ = 1
Cerca de 1, la separación coincide con Number.EPSILON.
Preguntas para explorar
- ¿Qué separación observás en
2⁰,2¹⁰y2²⁰? - Mové el control hasta
e = 52. ¿Qué sucede con las fracciones? - En
e = 53, ¿cuál es el siguiente entero representable? - ¿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.isFinitecuando el algoritmo requiere números finitos. - Usá
Number.isSafeIntegerpara 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
NaNo 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 deMathtrabajan 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
Numberutiliza 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-0requieren comprobaciones específicas.- Las comparaciones aproximadas deben combinar tolerancias absolutas y relativas.
BigIntpermite 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.