Number.sign () in javascript


101

Ti chiedi se ci sono modi non banali per trovare il segno del numero ( funzione segno )?
Possono essere soluzioni più brevi / veloci / più eleganti di quella ovvia

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

Risposta breve!

Usa questo e sarai sicuro e veloce (fonte: moz )

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

Potresti voler guardare le prestazioni e il violino di confronto del tipo

È passato molto tempo. Inoltre è principalmente per ragioni storiche.


risultati

Per ora abbiamo queste soluzioni:


1. Ovvio e veloce

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

1.1. Modifica da kbec : un tipo lanciato in meno, più performante, più corto [il più veloce]

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

attenzione: sign("0") -> 1


2. Elegante, corto, non così veloce [il più lento]

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

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

Poiché Infinityè un numero legale in JS, questa soluzione non sembra completamente corretta.


3. L'arte ... ma molto lenta [più lenta]

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

4. Utilizzando bit-shift
veloce, masign(-Infinity) -> 0

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

5. Type-safe [megafast]

! Sembra che i browser (in particolare il v8 di Chrome) effettuino alcune magiche ottimizzazioni e questa soluzione risulta essere molto più performante di altre, anche della (1.1) nonostante contenga 2 operazioni extra e logicamente non potrà mai essere più veloce.

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

Utensili

I miglioramenti sono i benvenuti!


[Offtopic] Risposta accettata

  • Andrey Tarantsov - +100 per l'arte, ma purtroppo è circa 5 volte più lento dell'approccio ovvio

  • Frédéric Hamidi - in qualche modo la risposta più votata (per il momento in cui scrivo) ed è piuttosto interessante, ma sicuramente non è come dovrebbero essere fatte le cose, imho. Inoltre non gestisce correttamente i numeri Infinity, che sono anche numeri, sai.

  • kbec - è un miglioramento della soluzione ovvia. Non così rivoluzionario, ma considerando tutto insieme considero questo approccio il migliore. Votalo :)


3
il punto è che a volte 0è un caso speciale
sfortunato il

1
Ho creato una serie di test JSPerf (con diversi tipi di input) per testare ogni algoritmo, che può essere trovato qui: jsperf.com/signs I risultati potrebbero non essere quelli elencati in questo post!
Alba Mendez

2
@ soddisfatto, quale di loro? Ovviamente, se esegui la test everythingversione, Safe si rifiuterà di testare i valori speciali, quindi sarà più veloce! Prova invece a eseguire il only integerstest. Inoltre, JSPerf sta solo facendo il suo lavoro, non è questione di piacergli. :)
Alba Mendez

2
Secondo i test jsperf risulta che typeof x === "number"mette un po 'di magia su performace. Per favore, fai più run, specialmente FF, Opera e IE per renderlo più chiaro.
sfortunato

4
Per completezza ho aggiunto un nuovo test jsperf.com/signs/7 per Math.sign()(0 === 0, non veloce come "Safe") che è apparso in FF25 ed è in arrivo su Chrome.
Alex K.

Risposte:



28

Anche la divisione del numero per il suo valore assoluto dà il suo segno. L'utilizzo dell'operatore AND logico di cortocircuito ci consente di 0creare casi speciali, quindi non finiamo per dividerlo:

var sign = number && number / Math.abs(number);

6
Probabilmente vorresti var sign = number && number / Math.abs(number);nel caso in cuinumber = 0
NullUserException

@NullUserException, hai assolutamente ragione, 0deve essere in casi speciali. Risposta aggiornata di conseguenza. Grazie :)
Frédéric Hamidi

Sei il migliore per ora. Ma spero che ci saranno più risposte in futuro.
sfregiato il

24

La funzione che stai cercando si chiama signum e il modo migliore per implementarla è:

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

3
Aspettare. C'è un errore: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); restituisce [-1, -1, -1, 0, 1] per (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); restituisce [-1, -1, 0, 1, 1]
disfatto il

13

Questo non dovrebbe supportare gli zeri con segno di JavaScript (ECMAScript)? Sembra funzionare quando si restituisce x anziché 0 nella funzione "megafast":

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

Ciò lo rende compatibile con una bozza di Math.sign ( MDN ) di ECMAScript :

Restituisce il segno della x, che indica se x è positivo, negativo o zero.

  • Se x è NaN, il risultato è NaN.
  • Se x è −0, il risultato è −0.
  • Se x è +0, il risultato è +0.
  • Se x è negativo e non −0, il risultato è −1.
  • Se x è positivo e non +0, il risultato è +1.

Meccanismo incredibilmente veloce e interessante, sono impressionato. Aspettando altri test.
kbec

10

Per le persone interessate a cosa sta succedendo con i browser più recenti, nella versione ES6 esiste un metodo Math.sign nativo . Puoi controllare il supporto qui .

Fondamentalmente si ritorna -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

4
var sign = number >> 31 | -number >>> 31;

Superveloce se non hai bisogno di Infinity e sai che il numero è un intero, trovato nel sorgente di openjdk-7: java.lang.Integer.signum()


1
Questo fallisce per piccole frazioni negative come -0,5. (Sembra che la fonte
provenga

1

Ho pensato di aggiungere questo solo per divertimento:

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

0 e NaN restituirà -1
funziona bene su +/- Infinito


1

Una soluzione che funziona su tutti i numeri, così come 0e -0, così come Infinitye -Infinity, è:

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

Vedere la domanda " +0 e -0 sono uguali? " Per ulteriori informazioni.


Attenzione: Nessuna di queste risposte, tra cui l'ormai standard di Math.signlavoro sarà sul caso 0vs -0. Questo potrebbe non essere un problema per te, ma in alcune implementazioni fisiche potrebbe essere importante.


0

È possibile spostare il numero e controllare il bit più significativo (MSB). Se MSB è un 1, il numero è negativo. Se è 0, il numero è positivo (o 0).


@ NullUserException Potrei ancora sbagliarmi, ma dalla mia lettura "Gli operandi di tutti gli operatori bit per bit vengono convertiti in interi con segno a 32 bit in ordine big-endian e in formato complemento a due". tratto da MDN
Brombomb

Sembra ancora un sacco di lavoro; devi ancora convertire 1 e 0 in -1 e 1, e anche 0 deve essere curato. Se l'OP lo volesse, sarebbe più facile da usarevar sign = number < 0 : 1 : 0
NullUserException

+1. Non c'è bisogno di cambiare però, puoi semplicemente fare n & 0x80000000come una maschera di bit. Per quanto riguarda la conversione a 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin

@davin È garantito che tutti i numeri funzionino con quella maschera di bit? L'ho collegato -5e32e si è rotto.
NullUserException

@NullUserException ఠ_ఠ, numeri che contengono lo stesso segno quando vengono applicati gli standard ToInt32. Se leggi lì (sezione 9.5) c'è un modulo che influenza il valore dei numeri poiché l'intervallo di un intero a 32 bit è inferiore all'intervallo del tipo Numero js. Quindi non funzionerà per quei valori o per gli infiniti. La risposta però mi piace ancora.
davin

0

Stavo per fare la stessa domanda, ma sono arrivato a una soluzione prima di aver finito di scrivere, ho visto questa Domanda già esistita, ma non ho visto questa soluzione.

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

sembra essere più veloce aggiungendo un ternario però (n >> 31) + (n>0?1:0)


Molto bella. Il tuo codice sembra un po 'più veloce di (1). (n> 0? 1: 0) è più veloce perché non viene lanciato alcun tipo. L'unico momento deludente è il segno (-Infinity) dà 0. Test aggiornati.
sfigurato il

0

È molto simile alla risposta di Martijn

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

Lo trovo più leggibile. Inoltre (o, a seconda del punto di vista, comunque), brontola anche cose che possono essere interpretate come un numero; ad esempio, ritorna -1quando viene presentato con '-5'.


0

Non vedo alcun senso pratico per restituire -0 e 0 da Math.signquindi la mia versione è:

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

Questa non è una funzione signum
sfortunato

0

I metodi che conosco sono i seguenti:

Math.sign (n)

var s = Math.sign(n)

Questa è la funzione nativa, ma è la più lenta a causa dell'overhead di una chiamata di funzione. Tuttavia, gestisce 'NaN' dove gli altri sotto possono semplicemente assumere 0 (cioè Math.sign ('abc') è NaN).

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

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

In questo caso solo il lato sinistro o destro può essere un 1 in base al segno. Ciò si traduce in 1-0(1), 0-1(-1) o 0-0(0).

La velocità di questo sembra collo e collo con il prossimo sotto in Chrome.

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

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

Utilizza lo "spostamento a destra di propagazione del segno". Fondamentalmente lo spostamento di 31 elimina tutti i bit tranne il segno. Se il segno è stato impostato, questo risulta in -1, altrimenti è 0. A destra di |esso risulta positivo convertendo il valore in booleano (0 o 1 [BTW: le stringhe non numeriche, come !!'abc', diventano 0 in questo caso, e not NaN]) utilizza quindi un'operazione OR bit per bit per combinare i bit.

Questa sembra la migliore prestazione media tra i browser (meglio in Chrome e Firefox almeno), ma non la più veloce in TUTTI. Per qualche ragione, l'operatore ternario è più veloce in IE.

n n <0 -1:? 1: 0

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

Il più veloce in IE per qualche motivo.

jsPerf

Test eseguiti: https://jsperf.com/get-sign-from-value


0

I miei due centesimi, con una funzione che restituisce gli stessi risultati di Math.sign, ovvero sign (-0) -> -0, sign (-Infinity) -> -Infinity, sign (null) -> 0 , sign (undefined) -> NaN, etc.

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

Jsperf non mi consente di creare un test o una revisione, mi dispiace non essere in grado di fornirti i test (ho provato jsbench.github.io, ma i risultati sembrano molto più vicini l'uno all'altro rispetto a Jsperf ...)

Se qualcuno potesse aggiungerlo a una revisione Jsperf, sarei curioso di vedere come si confronta con tutte le soluzioni fornite in precedenza ...

Grazie!

Jim.

MODIFICA :

Avrei dovuto scrivere:

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

( (+x && -1)invece di (x && -1)) per gestire sign('abc')correttamente (-> NaN)


0

Math.sign non è supportato su IE 11. Sto combinando la risposta migliore con la risposta Math.sign:

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

Ora si può usare Math.sign direttamente.


1
Mi hai spinto ad aggiornare la mia domanda. Sono passati 8 anni da quando è stato chiesto. Aggiornato anche il mio jsfiddle a es6 e window.performance api. Ma preferisco la versione di Mozilla come polyfill in quanto corrisponde al tipo coercing di Math.sign. Al giorno d'oggi le prestazioni non sono più una preoccupazione.
sfregiato il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.