9

Quiero verificar si un valor es negativo o no (lo estoy haciendo con Math.sign).

Math.sign(30) // 1
Math.sign(-10) // -1
Math.sign(0) // 0
Math.sign(-0) // -0

Pero si el valor es 0 o -0 no puedo saber exactamente si es negativo o positivo, debido a que:

Math.sign(30) === 1 -> true
Math.sign(-10) === -1 -> true
Math.sign(0) === -0 -> true // ¿Por qué? ¿No debería ser false?
Pollo
  • 1,980
  • 6
  • 21
JGuerra
  • 305
  • 1
  • 4
  • 16
  • 2
    Espero que nunca devuelva false, porque sería muy peligroso. En mátemáticas un -0 es igual a un +0. Así que también lo tiene que ser en javascript. La solución sería mirar el número como **String** en lugar de número. El problema es que muchos de los métodos que hay para pasar de número a string te eliminarán el signo `-` del 0. Pero puedes el `.toLocaleString('en')` que se recomienda en una de las respuestas de abajo. Después, puedes hacer un `.startsWith("-")`, por ejemplo, para saber si empieza con "-" – Julio Sep 30 '20 at 12:18
  • 1
    Matemáticamente no deberías basar tu ejercicio en que el cero tenga un signo (leí todas las respuestas y tus comentarios a ellas). La [ley de tricotomía](https://en.wikipedia.org/wiki/Trichotomy_(mathematics)) define que todo número real es positivo, negativo o cero. Así pues, uno no debería añadir algo que no se usa para expresar la naturaleza del número cero. Cero no pertenece a los reales positivos ni a los negativos; ponerle signo es un artilugio de algún lenguaje de programación específico, no de la matemática subyacente. Si buscas isNegative, cero no debe estar ahí; igual con isPositive. – Alfabravo Sep 30 '20 at 22:38
  • Cero no es positivo ni negativo, sino todo lo contrario :) PD. Buena pregunta! – Alfabravo Sep 30 '20 at 22:38
  • @Alfabravo, precisamente tal y como explico en mi respuesta, en complemento a 1 (que es el usado en javascript) un `0` puede tener signo positivo o negativo. En complemento a 2 (como usan otros lenguajes como C, Java, etc) esto no es posible. – OscarGarcia Oct 08 '20 at 21:02
  • @OscarGarcia Sí, eso es claro, pero no, uno no debería resolver un problema en lógica de negocio con una validación que depende de vericuetos del lenguaje (si deciden pasarse a Rust, digamos, habría que ver cómo funciona) siendo que la matemática es la misma siempre. – Alfabravo Oct 08 '20 at 21:05
  • 1
    Al final, para hacer cálculos, da igual que se use complemento a 1 o complemento a 2. En la gran mayoría de casos lo que cuenta es la precisión de la representación numérica y, si es insuficiente, usar algún tipo de datos que permita alcanzar la precisión necesaria para el cálculo requerido. Aplicaciones como Mathematica se usan en cálculo matemático precisamente por estos motivos, probablemente haya matemáticos a los que la precisión que ofrece Rust se le quede tan corta como la que ofrece Javascript. – OscarGarcia Oct 08 '20 at 21:14
  • 1
    Pregunta relacionada [¿Por qué mis programas no pueden hacer cálculos aritméticos correctamente?](https://es.stackoverflow.com/questions/197) – JackNavaRow Oct 08 '20 at 21:25

6 Answers6

11

Según la documentación:

  • Igualdad Same-value-zero:

    Similar a la igualdad same-value, pero +0 y -0 son considerados iguales.

  • Igualdad abstracta, igualdad estricta e igualdad same value en la especificación.

    En la especificación ES5, la comparación == queda descrita en Section 11.9.3, The Abstract Equality Algorithm. La comparación === en 11.9.6, The Strict Equality Algorithm. (Búscala y leela, son breves y fáciles de leer. Nota: lee el algoritmo de la igualdad estricta primero.) ES5 también describe, en Section 9.12, The SameValue Algorithm para uso interno del motor JS.

  • The Strict Equality Comparison Algorithm

    (Traducido)
    La comparación x === y, donde x e y son valores, produce true o false. Esta comparación se realiza de la siguiente manera:
    1. Si Type (x) es diferente de Type (y), devuelve false.
    2. Si Type (x) es Undefined, devuelve true.
    3. Si Type (x) es Null, devuelve true.
    4. Si Type (x) es Number, entonces:
    --- Si x es NaN, devuelve false.
    --- Si y es NaN, devuelve false.
    --- Si x es el mismo valor numérico que y, devuelve true.
    --- Si x es +0 e y es −0, devuelve true.
    --- Si x es −0 e y es +0, devuelve true.
    --- Sino retorna false.
    5. Si Type (x) es String, devuelve true si x e y son exactamente la misma secuencia de caracteres (la misma longitud y los mismos caracteres en las posiciones correspondientes); de lo contrario, devuelve falso.
    6. Si Type (x) es Bool, devuelve true si x e y son ambos verdaderos o ambos falsos; de lo contrario, devuelve false.
    7. Devuelve true si x e y se refieren al mismo objeto. De lo contrario, devuelve false.


¿Como diferenciar 0 de -0?

Podrías utilizar number.toLocaleString()

El método toLocaleString() devuelve una cadena con una representación de este número sensible al idioma.

Ejemplo:

let a = (0).toLocaleString('en'),
  b = (-0).toLocaleString('en');
  
console.log(`${a} === ${b} =>`, a === b);
Marcos
  • 30,626
  • 6
  • 28
  • 63
3

Tu pregunta, mas que pertenecer a Stack Overflow en Español, pertenece a Math Stack Exchange.

El conjunto de los números enteros Z, es aquel que contiene a los siguientes elementos:

Z = {...,-3,-2,-1,0,1,2,3,...}

¿Ves algún simbolo sobre el 0 o que se repita tal como lo hace -1 o 1?

El signo - adelante de un número indica que es el opuesto aditivo del número en cuestión. Ser el opuesto aditivo es una propiedad que tienen los Anillos, tales como otras (elemento neutro aditivo, unidad, elemento neutro multiplicativo). Y resulta que la propiedad de elemento neutro aditivo, establece:

Para todo elemento x distinto del neutro aditivo, existe un segundo elemento y que se llama opuesto aditivo y cumple x+ y es el neutro aditivo.

Como ves, no existe un inverso aditivo para el 0, así que hablar de -0 se entiende que es -1x0=0.

Los números Positivos (P) son aquellos que cumplen estas propiedades:

  • Para todos x, y en P, se cumple que x+y y xy también está en P
  • Todo numero x cumple una y solo una de estas propiedades: x está en P , x=0, -x está en P.

También, la relación de orden < se define así :

Se dice que x es menor que y y se escribe x<y si existe un número positivo p (es decir, p está en P) tal que x+p=y.

Así, si x es un número positivo, puedes escribir gracias a la propiedad del neutro aditivo que :

0 + x = x

esto significa que 0<x ya que existe un número positivo x (por hipótesis) tal que 0+x = x. Por eso, todos los números positivos están caracterizados por:

0<x
L F
  • 444
  • 2
  • 9
3

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.

OscarGarcia
  • 26,999
  • 3
  • 26
  • 61
  • 1
    Añado la explicacion de [Math.sign](https://tc39.es/ecma262/#sec-math.sign) _retornara n si el valor es NaN 0 ó -0_ , creo que falta explicar como funciona primero el Math.sign, luego vamos si -0 ==0 – JackNavaRow Oct 08 '20 at 21:11
  • Se me hizo tarde y me centré en explicar el motivo de la existencia del -0 y del 0 como números diferentes y con signo reconocible que en por qué se consideran iguales. Pero se me acabó el tiempo (escribo el comentario desde la cama) y hoy no puedo trasnochar :( gracias por el comentario, mañana procuraré mejorar la respuesta para centrarme en ese punto. – OscarGarcia Oct 08 '20 at 21:18
  • Considero esta respuesta como la mas acertada porque no depende del lenguaje de programacion sino de IEEE754, que enlazo y en la pagina comenta _"Los valores ceros son valores finitos con significando 0. Estos son ceros con signo, el bit de signo específica si un cero es +0 (cero positivo) o -0 (cero negativo)."_ – JackNavaRow Oct 08 '20 at 21:22
  • 2
    Gracias por los consejos @JackNavaRow . Creo que ahora mi respuesta ha quedado más clara, dejando explicaciones largas para el final :) – OscarGarcia Oct 09 '20 at 06:48
2

Matemáticamente 0 es igual a -0. Todo número multiplicado por 0 es 0:

-0 = -1 * 0 = 0

Es por ello que obtienes ese resultado utilizando la librería Math (y sucederá lo mismo en cualquier librería relacionada con matemáticas)

Ahora, si quieres distinguir si se tiene el signo - o no, entonces podrías hacer varios if-else pero tendrías que utilizar un método diferente de comparación. El método podría ser Object.is:

  if(numero > 0){
     // el número es positivo
  }
  else if(numero < 0){
    // el número es negativo
  }
  else if(Object.is(numero,-0)){
       // Es -0
  }
  else{
       // Es 0 ó +0
  }
Carlos
  • 1,787
  • 4
  • 12
  • Gracias por la ayuda Carlos. Eso es justamente lo que he intentado, pero cuando "-0" se convierte a string, automáticamente se le remueve el signo "-". No se porque. – JGuerra Sep 30 '20 at 01:09
  • 1
    He modificado el comentario. Ahora utilizo Object.is el cuál te dará el resultado deseado. – Carlos Sep 30 '20 at 18:40
1

En realidad no se muy bien cuál es tu pregunta ya que haces una pregunta en el título pero en el post comentas que deseas conocer otra cosa (distinguir cuando un valor es positivo o negativo).

Acabo de testear tu código y a mi sí que me devuelve los valores correctos (es decir, como esperarias). No obstante, también puedes utilizar la función abs, que devuelve el valor absoluto de una variable numérica, para poder hacer la distinción que buscas:

var a = 0;
var b = -0;

//otros valores:
var c = 5;
var d = -5;

console.log(a == Math.abs(a)); //true
console.log(b == Math.abs(b)); //true
console.log(c == Math.abs(c)); //true
console.log(d == Math.abs(d)); //false

Si lo que deseas es conocer si un valor es negativo o no, puedes hacerlo de varias maneras. Te propongo una:

var arr = [5,-6.9,0,-0];
var n = arr.length;

for(var i = 0; i<n ; i++){

  if(arr[i] > 0){
    console.log('el valor '+arr[i]+' es positivo');
  }
  else if(arr[i] < 0){
    console.log('el valor '+arr[i]+' es negativo');
  }
  else{
    console.log('el valor '+arr[i]+' es nulo');
  }
    
}
cooper
  • 2,198
  • 1
  • 8
  • 16
  • Me ha ayudado mucho tu respuesta. En el segundo ejemplo 0 y -0 se van automaticamente al ultimo else, donde dice que es valor nulo. ¿Y si yo quisiera también saber si el 0 es negativo o positivo? – JGuerra Sep 26 '20 at 20:30
  • @JGuerra, me alegro que te haya servido. En cuanto a tu pregunta, yo la verdad es que te diría que no te enfrascases demasiado en ese punto. Quiero decir: eso realmente te serviría o te ayudaría en algo? En realidad *0* y *-0* hacen referencia al mismo valor. De hecho es el único valor cuyo signo es superfluo ya que por la propia naturaleza de este elemento es el único que no necesita de un signo para ser descrito de forma unívoca (esto desde un punto de vista matemático). – cooper Sep 26 '20 at 20:45
  • Si, esto que dices es cierto. Desde el punto de vista matemático es lo mismo. Quería hacer esta estricta distinción (cuando es 0 es negativo o positivo) por que he creado un paquete en NPM que me sirve en los proyectos en que trabajo (https://www.npmjs.com/package/werkstatt). Allí tengo quen función que se llama "isNegative" y en ella deseaba saber si el argumento (en caso fuese 0) es negativo o positivo. Pero lo dejaré así por el momento. ¡Gracias! – JGuerra Sep 30 '20 at 01:12
1

En informática nos podríamos (en condicional, no siempre) encontrar representado el "-0", trabajando en binario con números de coma flotante, al obtener un '1' en el MBS (el bit más a la izquierda, que indica signo siendo 1 negativo), y 0s en el resto de bits. Por ejemplo si trabajamos en 32bits, el 1000 0000 0000 0000 0000 0000 0000 0000b seria considerado el "-0", pero podría ser que dependiera del lenguaje diferenciarlo de un +0 a la hora de usarlo o representarlo.

En este caso, JavaScript, podemos hacer el experimento de forzar ese número binario en concreto y mostrar el resultado:

var numero = 0;
console.log("Valor inicial (0000...d)= ",numero);
numero = ~numero;
console.log("Negamos los bits (111...d)=   ",numero);
numero = numero >>>1; // 0111... = máximo positivo.
console.log("Introducimos un 0 por la izquierda (0111...d)=  ",numero);
numero = ~numero;
console.log("Negamos los bits (1000...d)=   ",numero);
console.log("numero=-0? ",numero===-0,numero==-0);

function enBinario(num){ //Uso esta función porqué .toString(2) no muestra negativos.
    var n=num;
    var string="";
    do{
        string=(n&1)+string;
        n=n>>>1;
    }while (n!=0);
    return string+"b";
}
console.log("numero en binario: ",enBinario(numero));

Como vemos, JavaScript no muestra "-0" ni "0", ya sea porque representa el "1000...b" de un modo distinto (como indican en MDN ), o (y es solo una opinion) JavaScript, al ser de tipado débil (se encarga el de gestionar los tipos de números, al contrario de C por ejemplo), al ver que una variable supera los 32 bits, debe convertirlo a 64 bits automáticamente, de manera que "modifica" ese resultado, o sea, que ese número no es el 1000...b (pero eso no es más que una opinión).

Mi opinión se basa en que JavaScript "permite" resultados de más de 32bits pero no parecen coincidir con el binario mostrado.
Por ejemplo:

var numero = ~((~0)>>>1);

function enBinario(num){ //Uso esta función porqué .toString(2) no muestra negativos.
    var n=num;
    var string="";
    do{
        string=(n&1)+string;
        n=n>>>1;
    }while (n!=0);
    return string;
}

function enDec(string){ // de bin en string a decimal.
    var n=0;
    for (let bit=0; bit<string.length; bit++){
        if (string[string.length-bit-1]=="1") n=(n|(1<<bit));
    }
    return n;
}

console.log("numero "+numero+" en binario: ",enBinario(numero),"b");
numero-=123456789; // Supera el màximo de 32bits
console.log ("numero - 123456789 = supera los 32 bits");
console.log("numero "+numero+" en binario: ",enBinario(numero),"b");
console.log("La operacion inversa no corresponde al resultado");
console.log (enBinario(numero),"b en decimal= "+enDec(enBinario(numero))); // No corresponde.
console.log("Numero escrito a mano: ",0b1111000101001000011001011101011);

Conclusión. Javascript no tiene previsto el uso del -0 (un ejemplo claro es que -0<0=false) , y al intentar forzarlo hace cosas extrañas, así que, en mi opinión, si realmente quieres usar el -0, no uses Javascript.

Arnau Castellví
  • 2,370
  • 8
  • 14