En el JavaScript moderno, hay dos tipos de números:
-
Los números regulares en JavaScript se almacenan en formato de 64 bits IEEE-754, también conocidos como «números de doble precisión en coma flotante». Estos son los números que vamos a utilizar la mayor parte del tiempo, y hablaremos de ellos en este capítulo.
-
Números BigInt, para representar enteros de longitud arbitraria. A veces son necesarios, porque un número normal no puede superar
253
o ser menor que-253
. Como los bigint se utilizan en pocas áreas especiales, les dedicamos un capítulo especial BigInt. -
base=16 se utiliza para los colores hexadecimales, codificaciones de caracteres, etc, los dígitos pueden ser
0..9
oA..F
. -
Base=2 es sobre todo para depurar operaciones a nivel de bits, los dígitos pueden ser
0
o1
. - Base=36 es el máximo, los dígitos pueden ser
0..9
oA..Z
. Se utiliza todo el alfabeto latino para representar un número. Un caso curioso, pero útil para36
es cuando necesitamos convertir un identificador numérico largo en algo más corto, por ejemplo para hacer una url corta. Simplemente se puede representar en el sistema numérico con base36
:alert( 123456..toString(36) ); // 2n9c
Dos puntos para llamar a un métodoPor favor, ten en cuenta que dos puntos en123456..toString(36)
no es un error tipográfico. Si queremos llamar a un método directamente en un número, comotoString
en el ejemplo anterior, entonces tenemos que colocar dos puntos..
después de él.Si colocamos un solo punto:
123456.toString(36)
, entonces habría un error, porque la sintaxis de JavaScript implica la parte decimal después del primer punto. Y si colocamos un punto más, entonces JavaScript sabe que la parte decimal está vacía y ahora va el método.También podría escribir
(123456).toString(36)
.Redondeando
Una de las operaciones más utilizadas cuando se trabaja con números es el redondeo.
Hay varias funciones incorporadas para redondear:
Math.floor
Redondea hacia abajo:3.1
se convierte en3
, y-1.1
se convierte en-2
Math.ceil
Redondea:3.1
se convierte en4
, y-1.1
se convierte en-1
Math.round
Redondea al número entero más cercano:3.1
se convierte en3
3.6
se convierte en4
, el caso medio:3.5
redondea a4
también.Math.trunc
(no soportado por Internet Explorer) Elimina todo lo que hay después del punto decimal sin redondear:3.1
se convierte en3
-1.1
se convierte en-1
.Aquí está la tabla para resumir las diferencias entre ellos:
Math.floor
Math.ceil
.
Math.round
Math.trunc
3.1
3
4
3
3
3.6
3
4
4
3
-1.1
-2
-1
-1
-1
-1.6
-2
-1
-2
-1
Estas funciones cubren todas las formas posibles de tratar la parte decimal de un número. Pero, ¿qué pasa si queremos redondear el número a
n-th
dígito después del decimal?Por ejemplo, tenemos
1.2345
y queremos redondearlo a 2 dígitos, obteniendo sólo1.23
.Hay dos formas de hacerlo:
- Multiplicar y dividir.
Por ejemplo, para redondear el número al 2º dígito después del decimal, podemos multiplicar el número por
100
(o una potencia mayor de 10), llamar a la función de redondeo y volver a dividirlo.let num = 1.23456;alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
El método toFixed(n) redondea el número a
n
dígitos después del punto y devuelve una representación de cadena del resultado.let num = 12.34;alert( num.toFixed(1) ); // "12.3"
Esto redondea hacia arriba o hacia abajo al valor más cercano, de forma similar aMath.round
:let num = 12.36;alert( num.toFixed(1) ); // "12.4"
Tenga en cuenta que el resultado de
toFixed
es una cadena. Si la parte decimal es más corta de lo requerido, se añaden ceros al final:let num = 12.34;alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
Podemos convertirlo en un número usando el plus unario o una llamada
Number()
+num.toFixed(5)
.-
Infinity
(y-Infinity
) es un valor numérico especial que es mayor (menor) que cualquier cosa. -
NaN
representa un error. -
isNaN(value)
convierte su argumento en un número y luego comprueba que seaNaN
:alert( isNaN(NaN) ); // truealert( isNaN("str") ); // true
Pero ¿necesitamos esta función? No podemos usar simplemente la comparación=== NaN
? Lo sentimos, pero la respuesta es no. El valorNaN
es único en el sentido de que no es igual a nada, incluido a sí mismo:alert( NaN === NaN ); // false
isFinite(value)
convierte su argumento en un número y devuelvetrue
si es un número normal, noNaN/Infinity/-Infinity
:alert( isFinite("15") ); // truealert( isFinite("str") ); // false, because a special value: NaNalert( isFinite(Infinity) ); // false, because a special value: Infinity
- Funciona con
NaN
Object.is(NaN, NaN) === true
, eso es bueno. - Los valores
0
y-0
son diferentes:Object.is(0, -0) === false
, técnicamente es cierto, porque internamente el número tiene un bit de signo que puede ser diferente aunque todos los demás bits sean ceros. - Añadir
"e"
con la cuenta de ceros al número. Como:123e6
es lo mismo que123
con 6 ceros123000000
. - Un número negativo después de
"e"
hace que el número se divida por 1 con ceros dados. Por ejemplo,123e-6
significa0.000123
123
millonésimas). - Puede escribir números directamente en sistemas hexadecimales (
0x
), octales (0o
) y binarios (0b
). -
parseInt(str, base)
analiza la cadenastr
en un entero en sistema numérico conbase
2 ≤ base ≤ 36
. -
num.toString(base)
convierte un número en una cadena en el sistema numérico con elbase
dado. - Usa
parseInt/parseFloat
para la conversión «suave», que lee un número de una cadena y luego devuelve el valor que podían leer antes del error. - Redondea usando
Math.floor
Math.ceil
Math.trunc
Math.round
onum.toFixed(precision)
. - Asegúrese de recordar que hay una pérdida de precisión cuando se trabaja con fracciones.
- Vea el objeto Math cuando las necesite. La biblioteca es muy pequeña, pero puede cubrir las necesidades básicas.
A veces se utiliza
isFinite
para validar si un valor de cadena es un número regular:let num = +prompt("Enter a number", '');// will be true unless you enter Infinity, -Infinity or not a numberalert( isFinite(num) );
Tenga en cuenta que una cadena vacía o con espacios.se trata como
0
en todas las funciones numéricas, incluyendoisFinite
.Comparar conObject.is
Hay un método especial incorporadoObject.is
que compara valores como===
, pero es más fiable para dos casos límite:En todos los demás casos,
Object.is(a, b)
es lo mismo quea === b
.Esta forma de comparar se utiliza a menudo en la especificación de JavaScript. Cuando un algoritmo interno necesita comparar dos valores por ser exactamente iguales, utiliza
Object.is
(llamado internamente SameValue).parseInt y parseFloat
La conversión numérica usando un plus
+
oNumber()
es estricta. Si un valor no es exactamente un número, falla:alert( +"100px" ); // NaN
La única excepción son los espacios al principio o al final de la cadena, ya que se ignoran.
Pero en la vida real solemos tener valores en unidades, como
"100px"
o"12pt"
en CSS. También en muchos países el símbolo de la moneda va después de la cantidad, por lo que tenemos"19€"
y nos gustaría extraer un valor numérico de eso.Para eso están
parseInt
yparseFloat
.Se «lee» un número de una cadena hasta que no se puede. En caso de error, se devuelve el número recogido. La función
parseInt
devuelve un número entero, mientras queparseFloat
devolverá un número de punto flotante:alert( parseInt('100px') ); // 100alert( parseFloat('12.5em') ); // 12.5alert( parseInt('12.3') ); // 12, only the integer part is returnedalert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
Hay situaciones en las que
parseInt/parseFloat
devolveráNaN
. Ocurre cuando no se ha podido leer ningún dígito:alert( parseInt('a123') ); // NaN, the first symbol stops the process
El segundo argumento deparseInt(str, radix)
La funciónparseInt()
tiene un segundo parámetro opcional. Especifica la base del sistema numérico, por lo queparseInt
también puede analizar cadenas de números hexadecimales, binarios, etc:alert( parseInt('0xff', 16) ); // 255alert( parseInt('ff', 16) ); // 255, without 0x also worksalert( parseInt('2n9c', 36) ); // 123456
Otras funciones matemáticas
JavaScript tiene un objeto Math incorporado que contiene una pequeña biblioteca de funciones y constantes matemáticas.
Algunos ejemplos:
Math.random()
Devuelve un número aleatorio de 0 a 1 (sin incluir el 1).
alert( Math.random() ); // 0.1234567894322alert( Math.random() ); // 0.5435252343232alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...)
Math.min(a, b, c...)
Devuelve el mayor/menor del número arbitrario de argumentos.
alert( Math.max(3, 5, -10, 0, 1) ); // 5alert( Math.min(1, 2) ); // 1
Math.pow(n, power)
Devuelve
n
elevado a la potencia dada.alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
Hay más funciones y constantes en el objeto
Math
, incluyendo la trigonometría, que puedes encontrar en los documentos del objeto Math.Resumen
Para escribir números con muchos ceros:
Para diferentes sistemas numéricos:
Para convertir valores como
12pt
y100px
a un número:Para las fracciones:
Más funciones matemáticas:
.
Cálculos imprecisos
Internamente, un número se representa en formato de 64 bits IEEE-754, por lo que hay exactamente 64 bits para almacenar un número: 52 de ellos se utilizan para almacenar los dígitos, 11 de ellos almacenan la posición del punto decimal (son cero para los números enteros), y 1 bit es para el signo.
Si un número es demasiado grande, desbordaría el almacenamiento de 64 bits, dando potencialmente un infinito:
alert( 1e500 ); // Infinity
Lo que puede ser un poco menos obvio, pero ocurre con bastante frecuencia, es la pérdida de precisión.
Considere esta (¡falsa!) prueba:
alert( 0.1 + 0.2 == 0.3 ); // false
Así es, si comprobamos si la suma de
0.1
y0.2
es0.3
, obtenemosfalse
.¡Extraño! Qué es entonces si no
0.3
?alert( 0.1 + 0.2 ); // 0.30000000000000004
¡Ay! Aquí hay más consecuencias que una comparación incorrecta. Imagina que estás haciendo un sitio de e-shopping y el visitante pone
$0.10
y$0.20
productos en su carrito. El total del pedido será$0.30000000000000004
. Eso sorprendería a cualquiera.Pero, ¿por qué ocurre esto?
Un número se almacena en la memoria en su forma binaria, una secuencia de bits – unos y ceros. Pero fracciones como
0.1
0.2
que parecen simples en el sistema numérico decimal son en realidad fracciones interminables en su forma binaria.En otras palabras, ¿qué es
0.1
? Es uno dividido por diez1/10
, una décima parte. En el sistema numérico decimal tales números son fácilmente representables. Compáralo con un tercio:1/3
. Se convierte en una fracción interminable0.33333(3)
.Así, la división por potencias
10
tiene garantizado su buen funcionamiento en el sistema decimal, pero la división por3
no. Por la misma razón, en el sistema numérico binario, la división por potencias de2
está garantizada para funcionar, pero1/10
se convierte en una fracción binaria interminable.Simplemente no hay manera de almacenar exactamente 0.1 o exactamente 0,2 utilizando el sistema binario, al igual que no hay forma de almacenar un tercio como fracción decimal.
El formato numérico IEEE-754 resuelve esto redondeando al número más cercano posible. Estas reglas de redondeo normalmente no nos permiten ver esa «pequeña pérdida de precisión», pero existe.
Podemos verlo en acción:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
Y cuando sumamos dos números, sus «pérdidas de precisión» se suman.Por eso
0.1 + 0.2
no es exactamente0.3
.No sólo JavaScriptEl mismo problema existe en muchos otros lenguajes de programación.PHP, Java, C, Perl, Ruby dan exactamente el mismo resultado, porque se basan en el mismo formato numérico.
¿Podemos solucionar el problema? Claro, el método más fiable es redondear el resultado con la ayuda de un método toFixed(n):
let sum = 0.1 + 0.2;alert( sum.toFixed(2) ); // 0.30
Tenga en cuenta que
toFixed
siempre devuelve una cadena. Asegura que tiene 2 dígitos después del punto decimal. Esto es realmente conveniente si tenemos un e-shopping y necesitamos mostrar$0.30
. Para otros casos, podemos usar el unario más para coaccionarlo en un número:let sum = 0.1 + 0.2;alert( +sum.toFixed(2) ); // 0.3
También podemos multiplicar temporalmente los números por 100 (o por un número mayor) para convertirlos en enteros, hacer las cuentas y volver a dividir. Entonces, como estamos haciendo las matemáticas con enteros, el error disminuye un poco, pero lo seguimos teniendo en la división:alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
Así, el enfoque de multiplicar/dividir reduce el error, pero no lo elimina totalmente.
A veces podríamos tratar de evadir las fracciones en absoluto. Por ejemplo, si estamos tratando con una tienda, entonces podemos almacenar los precios en centavos en lugar de dólares. Pero, ¿y si aplicamos un descuento del 30%? En la práctica, eludir totalmente las fracciones rara vez es posible. Basta con redondearlas para cortar «colas» cuando sea necesario.
Lo curiosoPrueba a ejecutar esto:// Hello! I'm a self-increasing number!alert( 9999999999999999 ); // shows 10000000000000000
Esto sufre el mismo problema: una pérdida de precisión. Hay 64 bits para el número, 52 de ellos se pueden utilizar para almacenar dígitos, pero eso no es suficiente. Así que los dígitos menos significativos desaparecen.JavaScript no provoca un error en tales eventos. Hace lo que puede para encajar el número en el formato deseado, pero por desgracia, este formato no es lo suficientemente grande.
Dos cerosOtra consecuencia curiosa de la representación interna de los números es la existencia de dos ceros:
0
y-0
.Esto se debe a que un signo está representado por un solo bit, por lo que puede ponerse o no ponerse para cualquier número que incluya un cero.
En la mayoría de los casos la distinción es imperceptible, porque los operadores están adaptados para tratarlos como lo mismo.
Pruebas: isFinite e isNaN
¿Recuerdas estos dos valores numéricos especiales?
Pertenecen al tipo
number
, pero no son números «normales», por lo que hay funciones especiales para comprobarlos: -
- Multiplicar y dividir.
Así que aquí hablaremos de números regulares. Vamos a ampliar nuestros conocimientos sobre ellos.
Más formas de escribir un número
Imagina que necesitamos escribir mil millones. La forma obvia es:
let billion = 1000000000;
También podemos usar el guión bajo _
como separador:
let billion = 1_000_000_000;
_
juega el papel de «azúcar sintáctico», hace que el número sea más legible. El motor de JavaScript simplemente ignora _
entre los dígitos, por lo que es exactamente el mismo mil millones que el anterior.
Sin embargo, en la vida real, tratamos de evitar escribir largas secuencias de ceros. Somos demasiado perezosos para eso. Intentaremos escribir algo como "1bn"
para mil millones o "7.3bn"
para 7 mil 300 millones. Lo mismo ocurre con la mayoría de los números grandes.
En JavaScript, podemos acortar un número añadiendo la letra "e"
al mismo y especificando el número de ceros:
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroesalert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
En otras palabras, e
multiplica el número por 1
con la cuenta de ceros dada.
1e3 = 1 * 1000 // e3 means *10001.23e6 = 1.23 * 1000000 // e6 means *1000000
let ms = 0.000001;
Al igual que antes, usar "e"
puede ayudar. Si queremos evitar escribir los ceros explícitamente, podríamos decir lo mismo que:
let ms = 1e-6; // six zeroes to the left from 1
Si contamos los ceros en 0.000001
, son 6. Así que naturalmente es 1e-6
.
En otras palabras, un número negativo después de "e"
significa una división por 1 con el número de ceros dado:
// -3 divides by 1 with 3 zeroes1e-3 = 1 / 1000 (=0.001)// -6 divides by 1 with 6 zeroes1.23e-6 = 1.23 / 1000000 (=0.00000123)
Números hexadecimales, binarios y octales
Los números hexadecimales son ampliamente utilizados en JavaScript para representar colores, codificar caracteres y para muchas otras cosas. Así que, naturalmente, existe una forma más corta de escribirlos: 0x
y luego el número.
Por ejemplo:
alert( 0xff ); // 255alert( 0xFF ); // 255 (the same, case doesn't matter)
Los sistemas numéricos binarios y octales son poco utilizados, pero también se admiten utilizando los prefijos 0b
y 0o
:
let a; // binary form of 255let b = 0o377; // octal form of 255alert( a == b ); // true, the same number 255 at both sides
Sólo hay 3 sistemas numéricos con este soporte. Para otros sistemas numéricos, debemos utilizar la función parseInt
(que veremos más adelante en este capítulo).
aCadena(base)
El método num.toString(base)
devuelve una representación en forma de cadena de num
en el sistema numérico con el base
dado.
Por ejemplo:
let num = 255;alert( num.toString(16) ); // ffalert( num.toString(2) ); // 11111111
El base
puede variar desde 2
hasta 36
. Por defecto es 10
.
Los casos de uso comunes para esto son: