Number.sign () em javascript

101

Gostaria de saber se existem maneiras não triviais de encontrar o sinal de número ( função signum )?
Podem ser soluções mais curtas / rápidas / mais elegantes do que a óbvia

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

Resposta curta!

Use isso e você estará seguro e rápido (fonte: moz )

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

Você pode querer observar o desempenho e o violino de comparação de coerção de tipo

Muito tempo se passou. Além disso, é principalmente por razões históricas.


Resultados

Por enquanto, temos estas soluções:


1. Óbvio e rápido

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

1.1. Modificação de kbec - um tipo de elenco a menos, mais desempenho, mais curto [mais rápido]

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

Cuidado: sign("0") -> 1


2. Elegante, curto, não tão rápido [mais lento]

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

CUIDADO: sign(+-Infinity) -> NaN ,sign("0") -> NaN

Como Infinityé um número legal em JS, essa solução não parece totalmente correta.


3. A arte ... mas muito lenta [mais lenta]

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

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

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

5. Tipo seguro [megafast]

! Parece que os navegadores (especialmente o Chrome v8) fazem algumas otimizações mágicas e esta solução acaba tendo muito mais desempenho do que outras, até mesmo do que (1.1), apesar de conter 2 operações extras e logicamente nunca pode ser mais rápida.

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

Ferramentas

Melhorias são bem-vindas!


[Offtopic] Resposta aceita

  • Andrey Tarantsov - +100 para a arte, mas infelizmente é cerca de 5 vezes mais lento do que a abordagem óbvia

  • Frédéric Hamidi - de alguma forma a resposta mais votada (por enquanto) e é bem legal, mas definitivamente não é como as coisas deveriam ser feitas, imho. Além disso, ele não lida corretamente com números do Infinity, que também são números, você sabe.

  • kbec - é uma melhoria da solução óbvia. Não tão revolucionário, mas no conjunto considero esta abordagem a melhor. Vote nele :)

desfavorecidos
fonte
3
o ponto é que às vezes 0é um caso especial
desfigurado em
1
Eu fiz um conjunto de testes JSPerf (com diferentes tipos de entrada) para testar cada algoritmo, que pode ser encontrado aqui: jsperf.com/signs Os resultados podem não ser os listados neste post!
Alba Mendez
2
@disfated, qual deles? Claro, se você executar a test everythingversão, o Safe se recusará a testar os valores especiais, então será mais rápido! Em only integersvez disso, tente executar o teste. Além disso, JSPerf está apenas fazendo seu trabalho, não é uma questão de gostar. :)
Alba Mendez
2
De acordo com os testes do jsperf, isso typeof x === "number"coloca um pouco de mágica na performance. Por favor, faça mais execuções, especialmente FF, Opera e IE para deixar isso claro.
desfatada em
4
Para completar, adicionei um novo teste jsperf.com/signs/7 para Math.sign()(0 === 0, não tão rápido quanto "Seguro") que apareceu no FF25 e será lançado no Chrome.
Alex K.

Respostas:

79

Versão mais elegante de solução rápida:

var sign = number?number<0?-1:1:0
kbec
fonte
4
-1 por misturar seu ternáriovar sign = (number)? ((number < 0)? -1 : 1 ) : 0
Patrick Michaelsen
3
Math.sign(number)
Илья Зеленько
28

Dividir o número por seu valor absoluto também fornece seu sinal. Usar o operador AND lógico de curto-circuito nos permite criar casos especiais 0para não acabarmos nos dividindo por ele:

var sign = number && number / Math.abs(number);
Frédéric Hamidi
fonte
6
Você provavelmente vai querer var sign = number && number / Math.abs(number);no casonumber = 0
NullUserException
@NullUserException, você está absolutamente certo, 0precisa ser especial. Resposta atualizada em conformidade. Obrigado :)
Frédéric Hamidi,
Você é o melhor por enquanto. Mas espero que haja mais respostas no futuro.
desfavorecido em
24

A função que você está procurando é chamada signum , e a melhor maneira de implementá-la é:

function sgn(x) {
  return (x > 0) - (x < 0);
}
Andrey Tarantsov
fonte
3
Esperar. Há um erro: para (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); dá [-1, -1, -1, 0, 1] para (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); dá correto [-1, -1, 0, 1, 1]
desajustado de
13

Isso não deveria suportar zeros assinados de JavaScript (ECMAScript)? Parece funcionar ao retornar x em vez de 0 na função “megafast”:

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

Isso o torna compatível com um rascunho do Math.sign ( MDN ) do ECMAScript :

Retorna o sinal de x, indicando se x é positivo, negativo ou zero.

  • Se x for NaN, o resultado será NaN.
  • Se x for −0, o resultado será −0.
  • Se x for +0, o resultado será +0.
  • Se x for negativo e não −0, o resultado será −1.
  • Se x for positivo e não +0, o resultado será +1.
Martijn
fonte
Mecanismo incrivelmente rápido e interessante, estou impressionado. Esperando por mais testes.
kbec
10

Para quem está interessado no que está acontecendo com os navegadores mais recentes, na versão ES6 existe um método nativo Math.sign . Você pode verificar o suporte aqui .

Basicamente ele retorna -1, 1, 0ouNaN

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í
fonte
4
var sign = number >> 31 | -number >>> 31;

Super rápido se você não precisa do Infinity e sabe que o número é um inteiro, encontrado na fonte openjdk-7: java.lang.Integer.signum()

Toxiro
fonte
1
Isso falha para pequenas frações negativas como -0,5. (Parece que a fonte é de uma implementação especificamente para números inteiros)
starwed
1

Pensei em adicionar isto apenas por diversão:

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

0 e NaN retornará -1
funciona bem em +/- Infinity

jaya
fonte
1

Uma solução que funciona em todos os números, bem como 0e -0, bem como Infinitye -Infinity, é:

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

Consulte a pergunta " +0 e -0 são iguais? " Para obter mais informações.


Aviso: Nenhuma dessas respostas, incluindo o agora padrão Math.signtrabalho vontade sobre o caso 0vs -0. Isso pode não ser um problema para você, mas em certas implementações de física pode ser importante.

Andy Ray
fonte
0

Você pode alterar o número de bits e verificar o bit mais significativo (MSB). Se o MSB for 1, o número é negativo. Se for 0, o número é positivo (ou 0).

Brombomb
fonte
@ NullUserException Eu ainda posso estar errado, mas da minha leitura "Os operandos de todos os operadores bit a bit são convertidos em inteiros de 32 bits com sinal na ordem big-endian e no formato de complemento de dois." retirado de MDN
Brombomb,
Isso ainda parece muito trabalho; você ainda precisa converter 1 e 0 em -1 e 1, e 0 também precisa ser cuidado. Se o OP apenas quisesse isso, seria mais fácil usarvar sign = number < 0 : 1 : 0
NullUserException
+1. Não há necessidade de mudar, você pode apenas fazer n & 0x80000000como um bitmask. Quanto à conversão para 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin
@davin Todos os números funcionam com essa máscara de bits? Liguei -5e32e quebrou.
NullUserException
@NullUserException ఠ_ఠ, números que possuem o mesmo sinal quando aplicados os padrões ToInt32. Se você ler aqui (seção 9.5), há um módulo que afeta o valor dos números, uma vez que o intervalo de um inteiro de 32 bits é menor do que o intervalo do tipo número js. Portanto, não funcionará para esses valores ou para o infinito. Ainda gosto da resposta.
davin,
0

Eu estava prestes a fazer a mesma pergunta, mas cheguei a uma solução antes de terminar de escrever, vi que essa pergunta já existia, mas não vi essa solução.

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

parece ser mais rápido adicionando um embora ternário (n >> 31) + (n>0?1:0)

Moritz Roessler
fonte
Muito agradável. Seu código parece um pouco mais rápido do que (1). (n> 0? 1: 0) é mais rápido porque não há conversão de tipo. O único momento decepcionante é o sinal (-Infinity) dá 0. Testes atualizados.
desfavorecido em
0

Muito semelhante à resposta de Martijn é

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

Acho mais legível. Além disso (ou, dependendo do seu ponto de vista, no entanto), também groca coisas que podem ser interpretadas como um número; por exemplo, ele retorna -1quando apresentado com '-5'.

equaeghe
fonte
0

Não vejo nenhum sentido prático de retornar -0 e 0, Math.signentão minha versão é:

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
fonte
Esta não é uma função de signum
desfatada em
0

Os métodos que conheço são os seguintes:

Math.sign (n)

var s = Math.sign(n)

Esta é a função nativa, mas é a mais lenta de todas devido à sobrecarga de uma chamada de função. No entanto, ele lida com 'NaN', onde os outros abaixo podem apenas assumir 0 (ou seja, Math.sign ('abc') é NaN).

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

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

Neste caso, apenas o lado esquerdo ou direito pode ser 1 com base no sinal. Isso resulta em 1-0(1), 0-1(-1) ou0-0 (0).

A velocidade deste parece pescoço a pescoço com o próximo abaixo no Chrome.

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

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

Usa o "deslocamento para a direita de propagação de sinal". Basicamente, deslocar em 31 elimina todos os bits, exceto o sinal. Se o sinal foi definido, isso resulta em -1, caso contrário, é 0. À direita |dele testa positivo ao converter o valor em booleano (0 ou 1 [BTW: strings não numéricas, como!!'abc' , tornam-se 0 neste caso, e not NaN]) então usa uma operação OR bit a bit para combinar os bits.

Este parece ser o melhor desempenho médio entre os navegadores (melhor no Chrome e Firefox, pelo menos), mas não o mais rápido em TODOS eles. Por algum motivo, o operador ternário é mais rápido no IE.

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

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

Mais rápido no IE por algum motivo.

jsPerf

Testes realizados: https://jsperf.com/get-sign-from-value

James Wilkins
fonte
0

Meus dois centavos, com uma função que retorna os mesmos resultados que Math.sign faria, ou seja, sinal (-0) -> -0, sinal (-Infinito) -> -Infinito, sinal (nulo) -> 0 , sinal (indefinido) -> NaN, etc.

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

Jsperf não me deixa criar um teste ou revisão, desculpe por não ser capaz de fornecer testes (eu tentei jsbench.github.io, mas os resultados parecem muito mais próximos uns dos outros do que com Jsperf ...)

Se alguém pudesse adicioná-lo a uma revisão Jsperf, eu ficaria curioso para ver como ele se compara a todas as soluções fornecidas anteriormente ...

Obrigado!

Jim.

EDITAR :

Eu deveria ter escrito:

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

(em (+x && -1)vez de (x && -1)) a fim de lidar sign('abc')corretamente (-> NaN)

Jimshell
fonte
0

Math.sign não é compatível com o IE 11. Estou combinando a melhor resposta com Math.sign:

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

Agora, pode-se usar o Math.sign diretamente.

Sudip
fonte
1
Você me pressionou para atualizar minha pergunta. 8 anos se passaram desde que foi perguntado. Também atualizei meu jsfiddle para es6 e window.performance api. Mas eu prefiro a versão do Mozilla como um polyfill, pois corresponde à coerção de tipo de Math.sign. O desempenho não é uma grande preocupação hoje em dia.
desfavorecido em