Respuestas rápidas
¿Por qué -0 === 0?
Porque la definición de igualdad para datos de tipo Number
de ECMAScript lo declara así explícitamente:
6.1.6.1.13 Number::equal ( x, y )
La operación abstracta Number::equal
toma dos argumentos x
(un Number
) e y
(un Number
). Cuando se llama se realizan las siguientes comprobaciones:
- Si
x
es NaN
, devuelve false
.
- Si
x
es NaN
, devuelve false
.
- Si
x
es el mismo valor Number
que y
, devuelve true
.
- Si
x
es +0
e y
es -0
, devuelve true
.
- Si
x
es -0
e y
es +0
, devuelve true
.
- Devolver
false
.
¿Por qué existen dos valores diferentes para +0
y -0
?
En javascript se usa una codificación de coma flotante de 64 bits que usa signo y magnitud para representar un valor numérico. Esto implica que todos los números, incluído el 0
, pueden representarse con signo +
y con signo -
. Podríamos decir que existe simetría entre los números positivos y negativos.
En complemento a 2 no existe tal simetría.
TL;DR
El estándar ECMAScript usa para los tipos numéricos (Number) el formato IEEE 754-2019 que tiene 63 bits para datos y un bit exclusivo para indicar el signo (complemento a uno) y una serie de bits especiales reservados para distinguir los siguientes casos:
- Un conjunto de valores no numéricos: javascript se queda con un único
NaN
indistinguible, aunque la norma define un NaN
silencioso (qNaN
) y un NaN
de señalización (sNaN
) que pueden llevar una carga útil de 53 bits que puede usarse para depurar las causas del NaN
.
- Infinito positivo y negativo:
+Infinity
y -Infinity
.
Otros lenguajes como C, Java, etc, Javascript usan complemento a 2 con el resto de bits cuando el bit de signo es negativo, impidiendo tener dos valores 0. Usar complemento a 2 implica que el valor máximo y mínimo que se puede representar no son iguales.
Sin embargo no usar complemento a 2 en el cálculo del valor de los números negativos, como hace esta norma, permite que el valor 0
pueda tener el bit de signo activo o no, permitiendo la existencia de un 0
positivo y un 0
negativo.
Demostración:
Según esta norma, un entero sin exponente se representa por:
signo × mantisa × 2 ^ exponente
Donde el signo puede ser +1 o -1, la mantisa es un número de 53 bits (9,007,199,254,740,992
combinaciones) y el exponente de 11 bits que indica un número entre -1074 y 971 (en vez de -1024 a 1023).
Por lo que si el exponente forzamos que sea 0
(sean números enteros) se supone que cualquier número superior a 9,007,199,254,740,992
debería empezar a tener fallos de continuidad por no poder expresarse con un exponente 0
:
console.log(9007199254740989);
console.log(-9007199254740989);
console.log(9007199254740990);
console.log(-9007199254740990);
console.log(9007199254740991);
console.log(-9007199254740991);
console.log(9007199254740992);
console.log(-9007199254740992);
console.log(9007199254740993);
console.log(-9007199254740993);
console.log(9007199254740994);
console.log(-9007199254740994);
console.log(9007199254740995);
console.log(-9007199254740995);
console.log(9007199254740996);
console.log(-9007199254740996);
/* En cuanto el exponente pasa de 0 a otro valor superior, entonces dos o más
números consecutivos se podrían codificar con la misma secuencia de bits */
console.log(9007199254740992 === 9007199254740993);
console.log(-9007199254740992 === -9007199254740993);
console.log(9007199254740995 === 9007199254740996);
console.log(-9007199254740995 === -9007199254740996);
Aumentar el exponente significa que habrá menor precisión en los bits menos significativos, impidiendo la continuidad numérica que esperábamos obtener y dando lugar a que comparaciones numéricas que aparentemente deberían ser diferentes salen iguales y, además, los resultados son completamente simétricos entre positivos y negativos.
Este comportamiento, si se usara complemento a dos, no sería posible.