Aprende leyendo en orden

Números y aritmética — el tipo Number y Math

Aprende los números de JavaScript: el tipo Number, la aritmética, el redondeo con Math y los errores decimales.

Buena parte de lo que calcula una app web se reduce a números — el subtotal de un pedido, el impuesto, el envío, los puntos ganados. JavaScript no tiene tipos separados para enteros y decimales; maneja ambos con el mismo tipo Number.

Este artículo cubre los operadores aritméticos, incluyendo el resto y el exponente, el redondeo con Math, alinear cifras con toFixed, y los errores de precisión decimal que dan problemas en los cálculos con dinero.

Un solo tipo Number — Number y operadores aritméticos

Algunos lenguajes separan enteros y decimales en tipos distintos. Esta noción de "clase de valor" se llama tipo (la clasificación de un valor — si es un número, texto, etc.).

JavaScript no distingue entre enteros y decimales — 1200 y 0.08 son ambos del mismo tipo Number (el tipo que representa números). Nunca eliges un tipo al declarar una variable, pero a cambio dividir dos enteros puede dejar un resultado decimal.

A los símbolos que dirigen un cálculo se les llama operadores aritméticos (símbolos que representan un cálculo entre valores). Además de la suma +, la resta -, la multiplicación * y la división /, está % para el resto de una división y ** para exponentes.

Operadores aritméticos y sus resultados
a + bsumara - brestara * bmultiplicara / bdividira % brestoa ** bexponente7 / 2→ 3.57 % 2→ 12 ** 10→ 1024
Las 2 filas superiores son los operadores; la inferior es lo que producen. La división deja un decimal, % devuelve el resto, y ** devuelve la potencia.
console.log(1200 + 300);   // 1500
console.log(1200 - 300);   // 900
console.log(1200 * 3);     // 3600

// Incluso con dos enteros, queda un decimal si la división no es exacta
console.log(7 / 2);        // 3.5

// % es el resto, ** es la exponenciación
console.log(7 % 2);        // 1
console.log(2 ** 10);      // 1024

% es útil para encontrar un sobrante, o "lo que queda al repartir en grupos de un tamaño fijo". Para un total de 4380, 4380 % 100 te da 80 — la cantidad que sobra al contar en unidades de 100.

** te da exponentes, dejándote calcular cosas como 2 ** 10 — el número de bytes en un kilobyte.

Obtén el total y el promedio de los 3 pedidos de un día. Cada importe — orderA, orderB y orderC — ya está declarado.

① Muestra el total de los 3 pedidos.

② Muestra el importe promedio por pedido.

③ Muestra lo que sobra al contar el total en unidades de 100.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Redondear con Math — floor, ceil y round

No puedes poner el promedio, 1903.3333333333333, directamente en una factura. Importes y cantidades necesitan redondearse a un entero en algún punto, y si redondeas hacia abajo, hacia arriba o al más cercano depende de las reglas del negocio.

JavaScript agrupa estas funciones de redondeo bajo Math.

Introduzcamos primero el término función (una unidad con nombre que ejecuta un proceso fijo sobre un valor que le pasas, y devuelve un resultado). Obtener un resultado de vuelta a partir de un valor que pasaste se describe como "llamarla" y hacer que "devuelva" un valor.

Una función bajo Math (una colección integrada de funciones numéricas de JavaScript) se llama escribiendo Math, un punto, y luego el nombre de la función — como Math.floor(1903.33).

Math.floor redondea un decimal hacia abajo, Math.ceil lo redondea hacia arriba, y Math.round redondea al entero más cercano. Las tres solo devuelven un número nuevo — la variable que pasaste se mantiene igual.

3 funciones de redondeo, 3 resultados distintos
Math.floor(149.8)Redondea eldecimal abajo149Math.ceil(149.8)Redondea eldecimal arriba150Math.round(149.8)Redondea alentero más cercano150
Pasa el mismo 149.8 a cada una, y floor devuelve 149, mientras que ceil y round devuelven 150.
const points = 149.8;             // Puntos calculados a partir del importe de compra

console.log(Math.floor(points));  // 149  redondea hacia abajo
console.log(Math.ceil(points));   // 150  redondea hacia arriba
console.log(Math.round(points));  // 150  redondea al más cercano

console.log(points);              // 149.8  el valor original no cambia

// max devuelve el mayor de los valores que le pases
console.log(Math.max(0, 5000 - 5200));   // 0

Otra que usarás más allá del redondeo es Math.max. Devuelve el mayor de los valores que le pases, así que Math.max(0, -120) es 0.

Eso te da un piso para un cálculo que no quieres que baje de cero — útil para algo como "cuánto falta para el envío gratis".

Averigua cuánto falta para el envío gratis, y los puntos ganados, a partir del subtotal de un carrito. El subtotal cartSubtotal y el umbral de envío gratis freeShippingLine ya están declarados.

① Muestra cuánto falta para el envío gratis — no dejes que baje de cero una vez el subtotal supere el umbral.

② Muestra los puntos ganados — el 3.5% del subtotal — redondeados hacia abajo a un entero.

③ Muestra, redondeado hacia arriba, cuántos artículos más de 250 harían falta para llegar al envío gratis.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Alinear cifras con toFixed — devuelve un string

A veces, como con un importe promedio, quieres decidir de antemano cuántas cifras mostrar. Mostrar 1903.3333333333333 tal cual es difícil de leer, así que lo limpias a un decimal y muestras 1903.3.

Usa toFixed para eso.

toFixed es un método (una función que llamas escribiendo un punto después de un valor) — lo llamas con un punto después de un número. Un valor pasado dentro de los paréntesis se llama argumento (un valor pasado dentro de los paréntesis de una función o método). Pásale el número de decimales a conservar, como en toFixed(1), y redondea en la siguiente cifra y devuelve el resultado con esos decimales. Pero lo que devuelve es un string, no un número. Úsalo solo justo antes de mostrar un valor — para seguir calculando, usa el número original o Math.round.

Math.floor(value) pasa un valor a una función que vive dentro del contenedor Math; value.toFixed(digits) llama a una función que el propio valor lleva. Lo que va antes del punto te dice qué forma estás viendo.

Qué devuelven toFixed y Math.round
toFixed(1)Alinea cifras,devuelve string"1903.3"toFixed(0)Alinea cifras,devuelve string"1903"Math.round(x * 10)/ 10Devuelve el númeroredondeado1903.3
Ambos pueden parecer el mismo 1903.3, pero toFixed devuelve un string, mientras que Math.round devuelve un número.
const averageOrder = 1903.3333333333333;   // Importe promedio por pedido

console.log(averageOrder.toFixed(1));  // 1903.3  devuelto como string
console.log(averageOrder.toFixed(0));  // 1903    devuelto como string

// Para seguir calculando, redondea manteniéndote en número
console.log(Math.round(averageOrder * 10) / 10);  // 1903.3  sigue siendo número

El resultado de toFixed es solo para mostrar

Como lo que devuelve toFixed es un string, sumar o multiplicar ese resultado se comporta distinto que con un número. La diferencia entre strings y números, y cómo comprobar cuál tienes, se cubre en el artículo de conversión de tipos.

Formatea el descuento de un artículo en rebajas en algo mostrable. El precio de lista listPrice y el precio rebajado salePrice ya están declarados.

① Muestra el descuento dado, discountRate, sin redondear.

② Muestra el mismo porcentaje, alineado a un decimal.

③ Muestra el mismo porcentaje, alineado sin decimales.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Error de precisión decimal — por qué 0.1 + 0.2 no es 0.3

Encadena suficientes cálculos con decimales, y la respuesta puede desviarse ligeramente. Esto es lo que provoca errores como un total que falla por un centavo, así que veamos por qué pasa y cómo evitarlo antes de que te muerda.

Los computadores guardan los números en binario. 0.1 en decimal no se divide de forma exacta en binario — se convierte en una fracción que se repite infinitamente — así que se guarda como un valor distinto, muy cercano.

0.1 y 0.2 se guardan cada uno ligeramente desviados, así que sumarlos también se desvía ligeramente de 0.3.

Qué pasa al guardar un decimal en binario
0.1No se divideexacto en binarioSe guarda cercade 0.10.2No se divideexacto en binarioSe guarda cercade 0.20.1 + 0.2Suma dosvalores cercanosAlgo másde 0.3
Tanto 0.1 como 0.2 ya están ligeramente desviados en cuanto se guardan, así que la suma también se aleja de 0.3.
console.log(0.1 + 0.2);   // 0.30000000000000004

// Escala a enteros primero, calcula, y luego vuelve a escalar — sin desvío
console.log((0.1 * 10 + 0.2 * 10) / 10);   // 0.3

Guarda los importes como enteros

Cuantas más sumas de decimales encadenes, más se acumula el desvío. Para importes de dinero, guárdalos como unidades enteras y divide solo justo antes de mostrarlos — eso evita este error por completo.

Suma los pesos de dos productos para un cálculo de envío. Dos pesos, weightA y weightB, ya están declarados.

① Muestra el resultado de sumar los dos pesos directamente.

② Muestra ese mismo total, redondeado a un decimal, como número.

③ Escala ambos pesos por 10 a enteros, súmalos, divide entre 10, y muestra el resultado.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Valores de cálculos fallidos — NaN e Infinity

Cuando un cálculo no sale bien, JavaScript no se detiene — devuelve un valor especial y sigue adelante. Muchos errores donde un total sale en blanco o un importe se muestra mal vienen de este tipo de valor mezclado en un cálculo que simplemente siguió corriendo.

Un resultado que no se puede expresar como número es NaN (abreviatura de Not a Number — un valor que significa que el resultado no pudo convertirse en número). 0 / 0, al intentar promediar un día sin pedidos, es un ejemplo.

Dividir entre cero al revés, como en 5710 / 0, devuelve Infinity (un valor que representa el infinito). Ninguno se detiene con un error, así que puede que no lo notes hasta que aparezca en pantalla.

const orderTotal = 5710;
const orderCount = 0;                  // Ese día hubo 0 pedidos

console.log(orderTotal / orderCount);  // Infinity
console.log(0 / 0);                    // NaN

// Una vez que NaN se mezcla, todo cálculo posterior también se vuelve NaN
console.log(0 / 0 + 1000);             // NaN

Igual que Math, Number agrupa su propio conjunto de funciones relacionadas con números. Puedes comprobar si un valor es un número entero con Number.isInteger. Number.isInteger(3) devuelve true y Number.isInteger(2.5) devuelve false — un booleano (un valor que representa una de dos opciones: si algo es verdadero) — así que sirve para confirmar un valor como una cantidad que no debería permitir decimales.

Cómo comprobar si dos valores son iguales se cubre en el siguiente artículo, pero NaN es la excepción — ni siquiera es igual a sí mismo con ese método.

Así que comprobar NaN usa una verificación dedicada en su lugar. Eso se cubre, junto con convertir strings a números, en el artículo de conversión de tipos.

console.log(Number.isInteger(3));      // true
console.log(Number.isInteger(2.5));    // false
console.log(Number.isInteger(6 / 2));  // true   una división exacta es un entero

NaN e Infinity fluyen hacia el siguiente cálculo sin detenerse nunca con un error. Cuando un total o un promedio se muestra mal, sospecha primero de una división entre cero — suele ser la forma más rápida de encontrar la causa.

QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué pasa al ejecutar console.log(10 / 4);?

Pregunta 2¿Qué devuelve llamar a toFixed(2) sobre un número?

Pregunta 3¿Qué muestra console.log(0.1 + 0.2);?