Number.sign () en javascript

101

Me pregunto si hay formas no triviales de encontrar el signo del número ( función signum ).
Pueden ser soluciones más cortas / rápidas / más elegantes que la obvia

var sign = number > 0 ? 1 : number < 0 ? -1 : 0;

¡Respuesta corta!

Use esto y estará seguro y rápido (fuente: moz )

if (!Math.sign) Math.sign = function(x) { return ((x > 0) - (x < 0)) || +x; };

Es posible que desee ver el violín de comparación de rendimiento y coerción de tipos

Ha pasado mucho tiempo. Además es principalmente por razones históricas.


Resultados

Por ahora tenemos estas soluciones:


1. Obvio y rápido

function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }

1.1. Modificación de kbec : un tipo de lanzamiento menos, más eficaz , más corto [más rápido]

function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

precaución: sign("0") -> 1


2. Elegante, corto, no tan rápido [más lento]

function sign(x) { return x && x / Math.abs(x); }

PRECAUCIÓN: sign(+-Infinity) -> NaN ,sign("0") -> NaN

A partir de Infinityun número legal en JS, esta solución no parece del todo correcta.


3. El arte ... pero muy lento [más lento]

function sign(x) { return (x > 0) - (x < 0); }

4. Usando bit-shift
rápido, perosign(-Infinity) -> 0

function sign(x) { return (x >> 31) + (x > 0 ? 1 : 0); }

5. Tipo seguro [megafast]

! Parece que los navegadores (especialmente Chrome's v8) hacen algunas optimizaciones mágicas y esta solución resulta ser mucho más eficiente que otras, incluso que (1.1) a pesar de que contiene 2 operaciones adicionales y lógicamente nunca puede ser más rápida.

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}

Herramientas

¡Las mejoras son bienvenidas!


[Offtopic] Respuesta aceptada

  • Andrey Tarantsov - +100 por el arte, pero lamentablemente es aproximadamente 5 veces más lento que el enfoque obvio

  • Frédéric Hamidi : de alguna manera, la respuesta más votada (por el momento) y es un poco genial, pero definitivamente no es así como se deben hacer las cosas, en mi humilde opinión. Además, no maneja correctamente los números Infinity, que también son números, ya sabes.

  • kbec - es una mejora de la solución obvia. No es tan revolucionario, pero en conjunto considero que este enfoque es el mejor. Vota por él :)

desfasados
fuente
3
el punto es que a veces 0es un caso especial
desapareció el
1
Hice un conjunto de pruebas JSPerf (con diferentes tipos de entrada) para probar cada algoritmo, que se puede encontrar aquí: jsperf.com/signs ¡ Es posible que los resultados no sean los que se enumeran en esta publicación!
Alba Mendez
2
@disfated, ¿cuál de ellos? Por supuesto, si ejecuta la test everythingversión, Safe se negará a probar los valores especiales, ¡así que será más rápido! Intente ejecutar la only integersprueba en su lugar. Además, JSPerf solo está haciendo su trabajo, no es cuestión de que le guste. :)
Alba Méndez
2
Según las pruebas de jsperf, resulta que le typeof x === "number"da algo de magia al rendimiento. Por favor, haga más ejecuciones, especialmente FF, Opera e IE para que quede claro.
desahuciado
4
Para completar, agregué una nueva prueba jsperf.com/signs/7 para Math.sign()(0 === 0, no tan rápido como "Seguro") que apareció en FF25 y está por venir en Chrome.
Alex K.

Respuestas:

79

Versión más elegante de solución rápida:

var sign = number?number<0?-1:1:0
kbec
fuente
4
-1 por aplastar su ternario juntosvar sign = (number)? ((number < 0)? -1 : 1 ) : 0
Patrick Michaelsen
3
Math.sign(number)
Илья Зеленько
28

La división del número por su valor absoluto también da su signo. El uso del operador AND lógico de cortocircuito nos permite aplicar un caso especial 0para que no terminemos dividiendo por él:

var sign = number && number / Math.abs(number);
Frédéric Hamidi
fuente
6
Probablemente lo desee var sign = number && number / Math.abs(number);en el casonumber = 0
NullUserException
@NullUserException, tiene toda la razón, 0debe estar en mayúsculas especiales. Respuesta actualizada en consecuencia. Gracias :)
Frédéric Hamidi
Eres el mejor por ahora. Pero espero que haya más respuestas en el futuro.
diferido el
24

La función que está buscando se llama signum y la mejor manera de implementarla es:

function sgn(x) {
  return (x > 0) - (x < 0);
}
Andrey Tarantsov
fuente
3
Espere. Hay un error: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); da [-1, -1, -1, 0, 1] para (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); da [-1, -1, 0, 1, 1]
correcto
13

¿No debería esto admitir los ceros firmados de JavaScript (ECMAScript)? Parece funcionar cuando se devuelve x en lugar de 0 en la función "megafast":

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? x : NaN : NaN;
}

Esto lo hace compatible con un borrador de Math.sign ( MDN ) de ECMAScript :

Devuelve el signo de la x, que indica si x es positivo, negativo o cero.

  • Si x es NaN, el resultado es NaN.
  • Si x es −0, el resultado es −0.
  • Si x es +0, el resultado es +0.
  • Si x es negativo y no −0, el resultado es −1.
  • Si x es positivo y no +0, el resultado es +1.
Martijn
fuente
Mecanismo increíblemente rápido e interesante, estoy impresionado. Esperando más pruebas.
kbec
10

Para las personas que estén interesadas en lo que está sucediendo con los navegadores más recientes, en la versión ES6 hay un método Math.sign nativo . Puedes consultar el soporte aquí .

Básicamente se vuelve -1, 1, 0oNaN

Math.sign(3);     //  1
Math.sign(-3);    // -1
Math.sign('-3');  // -1
Math.sign(0);     //  0
Math.sign(-0);    // -0
Math.sign(NaN);   // NaN
Math.sign('foo'); // NaN
Math.sign();      // NaN
Salvador Dalí
fuente
4
var sign = number >> 31 | -number >>> 31;

Superrápido si no necesita Infinity y sabe que el número es un entero, que se encuentra en la fuente openjdk-7: java.lang.Integer.signum()

Toxiro
fuente
1
Esto falla para pequeñas fracciones negativas como -0,5. (Parece que la fuente es de una implementación para Integers específicamente)
Starwed
1

Pensé en agregar esto solo por diversión:

function sgn(x){
  return 2*(x>0)-1;
}

0 y NaN devolverá -1
funciona bien en +/- Infinity

jaya
fuente
1

Una solución que funciona con todos los números, así como con 0y -0, así como con Infinityy -Infinity, es:

function sign( number ) {
    return 1 / number > 0 ? 1 : -1;
}

Consulte la pregunta " ¿Son iguales +0 y -0? " Para obtener más información.


Advertencia: Ninguna de estas respuestas, incluyendo el ahora estándar Math.signde trabajo voluntad en el caso 0vs -0. Puede que esto no sea un problema para usted, pero en ciertas implementaciones de física puede ser importante.

Andy Ray
fuente
0

Puede cambiar el número y comprobar el bit más significativo (MSB). Si el MSB es un 1, entonces el número es negativo. Si es 0, entonces el número es positivo (o 0).

Brombomb
fuente
@ NullUserException Todavía podría estar equivocado, pero de mi lectura "Los operandos de todos los operadores bit a bit se convierten en enteros de 32 bits con signo en orden big-endian y en formato de complemento a dos". tomado de MDN
Brombomb
Eso todavía parece mucho trabajo; todavía tienes que convertir 1 y 0 en -1 y 1, y también hay que cuidar 0. Si el OP solo quisiera eso, sería más fácil de usarvar sign = number < 0 : 1 : 0
NullUserException
+1. Sin embargo, no es necesario cambiar, puede hacerlo n & 0x80000000como una máscara de bits. En cuanto a la conversión a 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin
@davin ¿Se garantiza que todos los números funcionen con esa máscara de bits? Lo enchufé -5e32y se rompió.
NullUserException
@NullUserException ఠ_ఠ, números que tienen el mismo signo cuando se aplican los estándares ToInt32. Si lee allí (sección 9.5), hay un módulo que afecta el valor de los números, ya que el rango de un entero de 32 bits es menor que el rango del tipo js Number. Entonces no funcionará para esos valores o los infinitos. Aunque todavía me gusta la respuesta.
davin
0

Estaba a punto de hacer la misma pregunta, pero llegué a una solución antes de terminar de escribir, vi que esta Pregunta ya existía, pero no vi esta solución.

(n >> 31) + (n > 0)

aunque parece ser más rápido al agregar un ternario (n >> 31) + (n>0?1:0)

Moritz Roessler
fuente
Muy agradable. Su código parece bastante más rápido que (1). (n> 0? 1: 0) es más rápido porque no tiene tipo de lanzamiento. El único momento decepcionante es el signo (-Infinito) da 0. Pruebas actualizadas.
diferido el
0

Muy similar a la respuesta de Martijn es

function sgn(x) {
    isNaN(x) ? NaN : (x === 0 ? x : (x < 0 ? -1 : 1));
}

Lo encuentro más legible. Además (o, según su punto de vista, sin embargo), también asimila cosas que pueden interpretarse como un número; por ejemplo, regresa -1cuando se le presenta '-5'.

Equaeghe
fuente
0

No veo ningún sentido práctico de devolver -0 y 0 de, Math.signpor lo que mi versión es:

function sign(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    }
    if (x === -Infinity || 1 / x < 0) {
        return -1;
    }
    return 1;
};

sign(100);   //  1
sign(-100);  // -1
sign(0);     //  1
sign(-0);    // -1
Alexander Shutau
fuente
Esta no es una función signum
eliminada el
0

Los métodos que conozco son los siguientes:

Math.sign (n)

var s = Math.sign(n)

Esta es la función nativa, pero es la más lenta de todas debido a la sobrecarga de una llamada de función. Sin embargo, maneja 'NaN' donde los otros a continuación pueden simplemente asumir 0 (es decir, Math.sign ('abc') es NaN).

((n> 0) - (n <0))

var s = ((n>0) - (n<0));

En este caso, solo el lado izquierdo o derecho puede ser un 1 según el signo. Esto da como resultado 1-0(1), 0-1(-1) o0-0 (0).

La velocidad de este parece estar a la par con el siguiente a continuación en Chrome.

(n >> 31) | (!! n)

var s = (n>>31)|(!!n);

Utiliza el "desplazamiento a la derecha de propagación de señales". Básicamente, el desplazamiento de 31 elimina todos los bits excepto el signo. Si se estableció el signo, esto da como resultado -1; de lo contrario, es 0. A la derecha, |se comprueba si es positivo al convertir el valor a booleano (0 o 1 [BTW: cadenas no numéricas, como!!'abc' , se convierten en 0 en este caso, y not NaN]) luego usa una operación OR bit a bit para combinar los bits.

Este parece el mejor rendimiento promedio en todos los navegadores (mejor en Chrome y Firefox al menos), pero no el más rápido en TODOS. Por alguna razón, el operador ternario es más rápido en IE.

n? n <0? -1: 1: 0

var s = n?n<0?-1:1:0;

Más rápido en IE por alguna razón.

jsPerf

Pruebas realizadas: https://jsperf.com/get-sign-from-value

James Wilkins
fuente
0

Mis dos centavos, con una función que devuelve los mismos resultados que Math.sign haría, es decir, signo (-0) -> -0, signo (-Infinito) -> -Infinito, signo (nulo) -> 0 , signo (indefinido) -> NaN, etc.

function sign(x) {
    return +(x > -x) || (x && -1) || +x;
}

Jsperf no me deja crear una prueba o revisión, lo siento por no poder proporcionarle pruebas (le di una oportunidad a jsbench.github.io, pero los resultados parecen mucho más cercanos entre sí que con Jsperf ...)

Si alguien pudiera agregarlo a una revisión de Jsperf, tendría curiosidad por ver cómo se compara con todas las soluciones dadas anteriormente ...

¡Gracias!

Jim.

EDITAR :

Debería haber escrito:

function sign(x) {
    return +(x > -x) || (+x && -1) || +x;
}

(en (+x && -1)lugar de (x && -1)) para manejar sign('abc')correctamente (-> NaN)

Jimshell
fuente
0

Math.sign no es compatible con IE 11. Estoy combinando la mejor respuesta con la respuesta de Math.sign:

Math.sign = Math.sign || function(number){
    var sign = number ? ( (number <0) ? -1 : 1) : 0;
    return sign;
};

Ahora, se puede usar Math.sign directamente.

sudip
fuente
1
Me empujaste a actualizar mi pregunta. Pasaron 8 años desde que se pidió. También actualicé mi jsfiddle a es6 y window.performance api. Pero prefiero la versión de mozilla como polyfill ya que coincide con la coerción de tipos de Math.sign. El rendimiento no es una gran preocupación hoy en día.
desaparecido el