01 · Punto de partida
Una suma correcta con un resultado inesperado
En la aritmética decimal, una décima más dos décimas son exactamente tres décimas. Sin embargo, JavaScript produce:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
El lenguaje no olvidó sumar. El problema aparece antes: 0.1 y 0.2 no pueden representarse exactamente con una cantidad finita de dígitos binarios. La operación utiliza aproximaciones muy cercanas y el resultado conserva una diferencia diminuta.
Comprender esta representación permite responder:
- ¿qué números puede almacenar exactamente
Number? - ¿por qué la separación entre números representables cambia con la magnitud?
- ¿cuándo falla una comparación con
===? - ¿qué significa que un entero sea seguro?
- ¿cuándo conviene usar enteros,
BigInto una biblioteca decimal?
02 · Un formato finito
El tipo Number y el punto flotante
JavaScript representa normalmente sus números mediante el formato binario de doble precisión IEEE 754. Cada valor ocupa 64 bits distribuidos conceptualmente en tres campos:
De forma simplificada, un número normal se interpreta como:
El exponente permite mover el punto binario para representar valores muy pequeños y muy grandes. La fracción conserva una cantidad limitada de dígitos significativos. Por eso se llama punto flotante.
Con 53 bits de precisión efectiva existen alrededor de 15 a 17 dígitos decimales significativos confiables, según el valor y la operación. No significa que todos los números con 16 cifras sean exactos: describe aproximadamente la capacidad del formato.
03 · Fracciones en otra base
Decimales que no terminan en binario
En base 10, una fracción tiene decimal finito si, una vez simplificada, su denominador contiene solamente factores 2 y 5. Por eso 1/2=0.5 y 1/20=0.05 terminan.
En base 2 solo existe el factor 2. Una fracción puede escribirse con una cantidad finita de bits cuando su denominador simplificado es una potencia de 2:
| Fracción | Decimal | Binario | ¿Finito? |
|---|---|---|---|
1/2 | 0.5 | 0.1₂ | Sí |
1/4 | 0.25 | 0.01₂ | Sí |
1/8 | 0.125 | 0.001₂ | Sí |
1/10 | 0.1 | 0.000110011…₂ | No, se repite |
El patrón binario de 0.1 continúa. El formato conserva los bits disponibles y redondea al valor representable más próximo. Lo mismo ocurre en decimal cuando escribimos 1/3≈0.3333.
04 · Una grilla que cambia
La separación depende de la magnitud
Los valores de punto flotante no forman una grilla de paso constante. Cerca de cero están muy juntos; para magnitudes grandes, la separación aumenta.
Number.EPSILON es la distancia entre 1 y el siguiente número representable mayor que 1:
console.log(Number.EPSILON);
// 2.220446049250313e-16
console.log(1 + Number.EPSILON === 1); // false
console.log(1 + Number.EPSILON / 2 === 1); // true
Pero Number.EPSILON no es la separación universal. Alrededor de 10¹⁶, sumar 1 puede no cambiar el valor:
const grande = 10 ** 16;
console.log(grande + 1 === grande); // true
console.log(grande + 2 === grande); // false
El exponente amplía el rango a costa de espaciar los valores disponibles. Esta relación entre rango y precisión es central en el punto flotante.
05 · Exactitud dentro de un rango
Enteros seguros
Todos los enteros entre −(2⁵³−1) y 2⁵³−1 pueden representarse exactamente. JavaScript publica los extremos mediante:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.isSafeInteger(42));
console.log(Number.isSafeInteger(9007199254740992));
Fuera de ese rango, distintos enteros matemáticos pueden compartir la misma representación:
const limite = Number.MAX_SAFE_INTEGER;
console.log(limite + 1); // 9007199254740992
console.log(limite + 2); // 9007199254740992
console.log(limite + 1 === limite + 2); // true
06 · El orden puede importar
Acumulación, cancelación y asociatividad
Cada operación puede redondear su resultado al valor representable más cercano. Repetir muchas operaciones pequeñas permite que las diferencias se acumulen:
let suma = 0;
for (let i = 0; i < 10; i += 1) suma += 0.1;
console.log(suma); // 0.9999999999999999
console.log(suma === 1); // false
La suma de punto flotante tampoco es asociativa en todos los casos:
const a = 1e16;
const b = -1e16;
const c = 1;
console.log((a + b) + c); // 1
console.log(a + (b + c)); // 0
En la segunda agrupación, sumar 1 a −10¹⁶ no modifica el número representado; luego se cancela con 10¹⁶. Restar cantidades grandes casi iguales también puede eliminar dígitos significativos útiles, fenómeno conocido como cancelación catastrófica.
07 · Igualdad aproximada
Comparar con tolerancias dependientes de la escala
Una comparación estricta es correcta cuando buscamos identidad exacta de los valores representados. Para resultados numéricos aproximados suele convenir una combinación de tolerancias absoluta y relativa:
function casiIguales(a, b, absoluta = 1e-12, relativa = 1e-9) {
if (![a, b].every(Number.isFinite)) return a === b;
const diferencia = Math.abs(a - b);
const escala = Math.max(Math.abs(a), Math.abs(b));
return diferencia <= Math.max(absoluta, relativa * escala);
}
console.log(casiIguales(0.1 + 0.2, 0.3)); // true
console.log(casiIguales(1e12 + 1, 1e12)); // true con estos parámetros
La tolerancia absoluta domina cerca de cero. La relativa crece con la magnitud. Los valores predeterminados del ejemplo no son universales: deben adaptarse al algoritmo, las unidades y el riesgo del dominio.
08 · Valores especiales
Infinity, NaN, subdesbordamiento y cero negativo
El formato incluye valores que requieren tratamiento explícito:
| Valor | Origen posible | Comprobación |
|---|---|---|
Infinity | Desbordamiento o división no nula por cero. | Number.isFinite(x) |
NaN | Operación numérica sin resultado definido. | Number.isNaN(x) |
0 por subdesbordamiento | Magnitud demasiado pequeña. | Comparar contexto y escala. |
−0 | Resultado negativo cuya magnitud llega a cero. | Object.is(x, -0) |
console.log(Number.MAX_VALUE * 2); // Infinity
console.log(0 / 0); // NaN
console.log(Number.MIN_VALUE / 2); // 0
console.log(Object.is(-0, 0)); // false
console.log(-0 === 0); // true
NaN no es igual a sí mismo con ===. Por eso se usa Number.isNaN, no una comparación directa.
09 · Elegir otra representación
BigInt, unidades enteras y decimales exactos
No todos los problemas deben resolverse con Number:
| Necesidad | Estrategia | Consideración |
|---|---|---|
| Enteros arbitrariamente grandes | BigInt | No se mezcla directamente con Number. |
| Dinero con escala fija | Enteros en centavos | Definir reglas para impuestos y redondeos. |
| Decimales exactos generales | Biblioteca decimal | Mayor costo y API específica. |
| Cálculo científico aproximado | Number | Controlar escala, estabilidad y error. |
const grande = 9_007_199_254_740_993n;
console.log(grande + 1n); // 9007199254740994n
const precioCentavos = 1999;
const cantidad = 3;
console.log(precioCentavos * cantidad); // 5997 centavos
BigInt resuelve enteros grandes, no fracciones. Para convertir entre tipos hay que hacerlo de forma explícita y aceptar la posible pérdida al pasar a Number.
10 · Exploración
Laboratorio: observar el error acumulado
Elegí un experimento y la cantidad de pasos. El gráfico muestra la diferencia entre el cálculo con Number y el resultado decimal o entero esperado.
La diferencia aparece paso a paso
sumar 0.1 repetidamente
Diez sumas de 0.1 no coinciden exactamente con 1 en punto flotante.
El eje horizontal representa la iteración y el vertical la diferencia respecto del valor esperado. La línea dorada marca diferencia cero.
Preguntas para explorar
- Compará 0.1 con 0.125. ¿Por qué una acumulación presenta diferencia y la otra no?
- Con 0.01, observá si el signo de la diferencia permanece siempre igual.
- Sumá 1 cerca de
2⁵³. ¿En qué pasos deja de cambiar el resultado? - Aumentá y reducí los pasos. ¿La diferencia crece siempre de forma monótona?
11 · Decisiones de implementación
Patrones para cálculos más confiables
No existe una única solución para todos los problemas, pero estas prácticas reducen sorpresas:
- validar con
Number.isFinitelas entradas que deben ser ordinarias; - usar
Number.isSafeIntegercuando cada entero debe distinguirse; - comparar resultados aproximados mediante tolerancias justificadas;
- evitar restar números enormes casi iguales cuando existe una fórmula equivalente más estable;
- acumular dinero en unidades enteras mínimas cuando la escala es fija;
- conservar precisión durante el cálculo y formatear al presentar;
- probar valores cercanos a cero, extremos de escala y límites de representación.
Sumar muchos valores con compensación
Para sumas extensas, el algoritmo de Kahan conserva una compensación por bits perdidos:
function sumaKahan(valores) {
let suma = 0;
let compensacion = 0;
for (const valor of valores) {
const corregido = valor - compensacion;
const siguiente = suma + corregido;
compensacion = (siguiente - suma) - corregido;
suma = siguiente;
}
return suma;
}
console.log(sumaKahan(Array(10).fill(0.1))); // 1
La compensación puede mejorar la suma, pero no transforma Number en aritmética exacta ni resuelve todos los problemas de estabilidad.
12 · Comprensión
Confusiones frecuentes
«JavaScript calcula mal 0.1 + 0.2»
Calcula con los valores binarios representables más cercanos. La diferencia proviene de la representación finita, compartida por muchos lenguajes.
«Number.EPSILON es el error máximo de cualquier operación»
No. Describe la separación alrededor de 1. La escala, la cantidad de operaciones y la estabilidad del algoritmo influyen en el error.
«Si Number.isInteger devuelve true, el entero es exacto»
No necesariamente. Fuera del rango seguro pueden perderse enteros aunque el valor almacenado no tenga parte fraccionaria.
«BigInt sirve para decimales exactos»
BigInt representa enteros. Las cantidades decimales requieren una escala entera convenida o una representación decimal específica.
«Formatear con toFixed corrige el cálculo original»
Produce texto redondeado para mostrar. No cambia las operaciones anteriores ni garantiza reglas decimales exactas.
13 · Práctica guiada
Ejercicios de comprensión
Ejercicio 1: fracciones binarias
Indicá cuáles pueden representarse con una cantidad finita de bits: 1/2, 3/8, 1/5 y 7/16.
Ver solución razonada
1/2, 3/8 y 7/16 tienen denominadores potencia de 2 y representación binaria finita. 1/5 necesita una expansión binaria repetitiva.
Ejercicio 2: asociatividad
Explicá por qué (1e16 + −1e16) + 1 produce 1, pero 1e16 + (−1e16 + 1) produce 0.
Ver solución
En la primera agrupación, los valores grandes se cancelan exactamente antes de sumar 1. En la segunda, el 1 se pierde al sumarse con −1e16, porque la separación representable en esa escala es mayor; después queda la cancelación a cero.
Ejercicio 3: dinero en enteros
Un producto cuesta 19.99 y se compran 7 unidades. Representá el cálculo en centavos y devolvé pesos con dos decimales solo al presentar.
Ver solución
El precio es 1999 centavos. 1999·7=13993 centavos, equivalentes a 139.93. El cálculo entero evita acumular aproximaciones binarias de los centavos.
Ejercicio 4: validar enteros
Escribí una función que acepte solamente identificadores enteros seguros y positivos.
Ver solución y código
function esIdentificadorValido(valor) {
return Number.isSafeInteger(valor) && valor > 0;
}
console.log(esIdentificadorValido(123));
console.log(esIdentificadorValido(1.5));
console.log(esIdentificadorValido(Number.MAX_SAFE_INTEGER + 1));
// true, false, false
14 · Síntesis
Ideas para recordar
Numberusa una representación binaria finita de doble precisión.- Fracciones como 0.1 no tienen expansión binaria finita y se almacenan aproximadamente.
- La separación entre valores representables aumenta con la magnitud.
Number.EPSILONdescribe la separación alrededor de 1, no un error universal.- Los enteros son exactos solamente dentro del rango seguro.
- El orden de las operaciones puede cambiar un resultado de punto flotante.
- Las comparaciones aproximadas suelen necesitar tolerancias absolutas y relativas.
Infinity,NaN, subdesbordamiento y−0requieren controles explícitos.BigInt, enteros escalados y bibliotecas decimales resuelven necesidades diferentes.
En el próximo tema aprenderemos a interpretar tablas de valores: detectar tendencias, comparar cambios y reconocer estabilidad o comportamientos anómalos en datos numéricos.