Number.sign () en javascript


101

Je me demande s'il existe des moyens non triviaux de trouver le signe des nombres ( fonction signum )?
Peut être des solutions plus courtes / plus rapides / plus élégantes que l'évidente

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

Réponse courte!

Utilisez ceci et vous serez sûr et rapide (source: moz )

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

Vous voudrez peut-être examiner les performances et le violon de comparaison contraignant sur le type

Le temps a passé. De plus, c'est principalement pour des raisons historiques.


Résultats

Pour l'instant, nous avons ces solutions:


1. Évident et rapide

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

1.1. Modification de kbec - un type cast moins, plus performant, plus court [plus rapide]

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

mise en garde: sign("0") -> 1


2. Élégant, court, pas si rapide [le plus lent]

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

Mise en garde: sign(+-Infinity) -> NaN ,sign("0") -> NaN

À partir d' Infinityun numéro légal dans JS, cette solution ne semble pas tout à fait correcte.


3. L'art ... mais très lent [le plus lent]

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

4. Utilisation rapide du bit-shift
, maissign(-Infinity) -> 0

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

5. Type-safe [megafast]

! On dirait que les navigateurs (en particulier le v8 de chrome) font des optimisations magiques et cette solution s'avère beaucoup plus performante que d'autres, même que (1.1) malgré qu'elle contienne 2 opérations supplémentaires et ne peut logiquement jamais être plus rapide.

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

Outils

Les améliorations sont les bienvenues!


[Offtopic] Réponse acceptée

  • Andrey Tarantsov - +100 pour l'art, mais malheureusement, c'est environ 5 fois plus lent que l'approche évidente

  • Frédéric Hamidi - en quelque sorte la réponse la plus votée (pour le moment) et c'est plutôt cool, mais ce n'est certainement pas la façon dont les choses devraient être faites, à mon humble avis. De plus, il ne gère pas correctement les nombres Infinity, qui sont aussi des nombres, vous savez.

  • kbec - est une amélioration de la solution évidente. Pas si révolutionnaire, mais dans l'ensemble, je considère que cette approche est la meilleure. Votez pour lui :)


3
le fait est que parfois 0est un cas particulier
défait

1
J'ai fait un ensemble de tests JSPerf (avec différents types d'entrée) pour tester chaque algorithme, que vous pouvez trouver ici: jsperf.com/signs Les résultats peuvent ne pas être ceux listés dans cet article!
Alba Mendez

2
@ satisfait, lequel d'entre eux? Bien sûr, si vous exécutez la test everythingversion, Safe refusera de tester les valeurs spéciales, donc ce sera plus rapide! Essayez only integersplutôt d' exécuter le test. De plus, JSPerf fait juste son travail, il ne s'agit pas de l'aimer. :)
Alba Mendez

2
D'après les tests jsperf, il s'avère que cela typeof x === "number"met un peu de magie sur performace. S'il vous plaît, faites plus de courses, en particulier FF, Opera et IE pour que ce soit clair.
défait

4
Pour être complet, j'ai ajouté un nouveau test jsperf.com/signs/7 pour Math.sign()(0 === 0, pas aussi rapide que "Safe") qui est apparu dans FF25 et est à venir dans Chrome.
Alex K.

Réponses:



28

Diviser le nombre par sa valeur absolue donne également son signe. L'utilisation de l'opérateur logique AND de court-circuit nous permet de faire un cas particulier 0afin de ne pas finir par diviser par lui:

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

6
Vous voudriez probablement var sign = number && number / Math.abs(number);au cas oùnumber = 0
NullUserException

@NullUserException, vous avez tout à fait raison, 0doit être avec une casse spéciale. Réponse mise à jour en conséquence. Merci :)
Frédéric Hamidi

Vous êtes le meilleur pour le moment. Mais j'espère qu'il y aura plus de réponses à l'avenir.
défait

24

La fonction que vous recherchez s'appelle signum , et la meilleure façon de l'implémenter est:

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

3
Attendre. Il y a une erreur: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); donne [-1, -1, -1, 0, 1] pour (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); donne correct [-1, -1, 0, 1, 1]
défait le

13

Cela ne devrait-il pas prendre en charge les zéros signés de JavaScript (ECMAScript)? Cela semble fonctionner en renvoyant x plutôt que 0 dans la fonction «megafast»:

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

Cela le rend compatible avec un brouillon de Math.sign ( MDN ) d' ECMAScript :

Renvoie le signe du x, indiquant si x est positif, négatif ou nul.

  • Si x est NaN, le résultat est NaN.
  • Si x est −0, le résultat est −0.
  • Si x est +0, le résultat est +0.
  • Si x est négatif et non −0, le résultat est −1.
  • Si x est positif et non +0, le résultat est +1.

Mécanisme incroyablement rapide et intéressant, je suis impressionné. En attente de plus de tests.
kbec

10

Pour les personnes intéressées par ce qui se passe avec les derniers navigateurs, dans la version ES6, il existe une méthode native Math.sign . Vous pouvez consulter le support ici .

Fondamentalement , il retourne -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

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

Superfast si vous n'avez pas besoin d'Infinity et savez que le nombre est un entier, trouvé dans openjdk-7 source: java.lang.Integer.signum()


1
Cela échoue pour les petites fractions négatives comme -0,5. (On dirait que la source provient d'une implémentation pour les nombres entiers spécifiquement)
commencé le

1

J'ai pensé ajouter ceci juste pour le plaisir:

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

0 et NaN renverra -1
fonctionne bien sur +/- Infinity


1

Une solution qui fonctionne sur tous les nombres, ainsi que sur 0et -0, ainsi que sur Infinityet -Infinity, est:

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

Voir la question « Est-ce que +0 et -0 sont identiques? » Pour plus d'informations.


Avertissement: Aucune de ces réponses, y compris le maintenant standard Math.signtravaillera sur le cas 0vs -0. Ce n'est peut-être pas un problème pour vous, mais dans certaines implémentations physiques, cela peut avoir de l'importance.


0

Vous pouvez décaler le nombre de bits et vérifier le bit le plus significatif (MSB). Si le MSB est un 1, le nombre est négatif. S'il vaut 0, le nombre est positif (ou 0).


@ NullUserException Je pourrais toujours me tromper, mais d'après ma lecture "Les opérandes de tous les opérateurs bit à bit sont convertis en entiers 32 bits signés dans l'ordre big-endian et au format complément à deux." extrait de MDN
Brombomb

Cela semble encore être un énorme travail; vous devez toujours convertir 1 et 0 en -1 et 1, et 0 doit également être pris en charge. Si l'OP voulait juste cela, ce serait plus facile à utiliservar sign = number < 0 : 1 : 0
NullUserException

+1. Pas besoin de changer, vous pouvez faire n & 0x80000000comme un bitmask. Quant à la conversion en 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin

@davin Tous les nombres sont-ils garantis pour fonctionner avec ce masque de bits? Je me suis branché -5e32et il s'est cassé.
NullUserException

@NullUserException ఠ_ఠ, des nombres qui portent le même signe lors de l'application des normes ToInt32. Si vous y lisez (section 9.5), il existe un module qui affecte la valeur des nombres puisque la plage d'un entier 32 bits est inférieure à la plage du type js Number. Cela ne fonctionnera donc pas pour ces valeurs ou les infinis. J'aime toujours la réponse.
davin

0

J'étais sur le point de poser la même question, mais je suis venu à une solution avant d'avoir fini d'écrire, j'ai vu que cette question existait déjà, mais je n'ai pas vu cette solution.

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

cela semble être plus rapide en ajoutant un ternaire cependant (n >> 31) + (n>0?1:0)


Très agréable. Votre code semble un peu plus rapide que (1). (n> 0? 1: 0) est plus rapide en l'absence de conversion de type. Le seul moment décevant est le signe (-Infinity) donne 0. Tests mis à jour.
défait le

0

Très similaire à la réponse de Martijn est

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

Je le trouve plus lisible. Aussi (ou, selon votre point de vue, cependant), il groks aussi des choses qui peuvent être interprétées comme un nombre; par exemple, il revient -1lorsqu'il est présenté avec '-5'.


0

Je ne vois aucun sens pratique de retour de -0 et 0 à partir de Math.signdonc ma version est:

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

Ce n'est pas une fonction signum
défait

0

Les méthodes que je connais sont les suivantes:

Math.sign (n)

var s = Math.sign(n)

C'est la fonction native, mais elle est la plus lente de toutes en raison de la surcharge d'un appel de fonction. Il gère cependant 'NaN' où les autres ci-dessous peuvent simplement supposer 0 (c'est-à-dire que Math.sign ('abc') est NaN).

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

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

Dans ce cas, seul le côté gauche ou droit peut être un 1 basé sur le signe. Cela se traduit par 1-0(1), 0-1(-1) ou0-0 (0).

La vitesse de celui-ci semble au coude à coude avec le suivant ci-dessous dans Chrome.

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

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

Utilise le "décalage vers la droite de propagation de signe". Fondamentalement, le décalage de 31 supprime tous les bits sauf le signe. Si le signe a été défini, cela donne -1, sinon il vaut 0. À droite, |il teste le positif en convertissant la valeur en booléen (0 ou 1 [BTW: chaînes non numériques, comme!!'abc' , deviennent 0 dans ce cas, et not NaN]) utilise alors une opération OR au niveau du bit pour combiner les bits.

Cela semble être la meilleure performance moyenne sur tous les navigateurs (au moins dans Chrome et Firefox), mais pas la plus rapide dans TOUS. Pour une raison quelconque, l'opérateur ternaire est plus rapide dans IE.

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

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

Le plus rapide dans IE pour une raison quelconque.

jsPerf

Tests effectués: https://jsperf.com/get-sign-from-value


0

Mes deux cents, avec une fonction qui renvoie les mêmes résultats que Math.sign ferait, c'est-à-dire signe (-0) -> -0, signe (-Infinity) -> -Infinity, signe (null) -> 0 , signe (non défini) -> NaN, etc.

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

Jsperf ne me laisse pas créer de test ou de révision, désolé de ne pas pouvoir vous fournir de tests (j'ai essayé jsbench.github.io, mais les résultats semblent beaucoup plus proches les uns des autres qu'avec Jsperf ...)

Si quelqu'un pouvait s'il vous plaît l'ajouter à une révision Jsperf, je serais curieux de voir comment cela se compare à toutes les solutions précédemment données ...

Je vous remercie!

Jim.

MODIFIER :

J'aurais dû écrire:

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

( (+x && -1)au lieu de (x && -1)) pour gérer sign('abc')correctement (-> NaN)


0

Math.sign n'est pas pris en charge sur IE 11. Je combine la meilleure réponse avec la réponse Math.sign:

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

Maintenant, on peut utiliser directement Math.sign.


1
Vous m'avez poussé à mettre à jour ma question. 8 ans se sont écoulés depuis qu'il a été demandé. J'ai également mis à jour mon jsfiddle vers l'api es6 et window.performance. Mais je préfère la version de mozilla comme polyfill car elle correspond à la contrainte de type de Math.sign. La performance n'est pas vraiment un problème de nos jours.
défait
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.