Number.sign () w javascript

101

Ciekawe, czy są jakieś nietrywialne sposoby znalezienia znaku liczby ( funkcja signum )?
Mogą być krótsze / szybsze / bardziej eleganckie rozwiązania niż oczywiste

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

Krótka odpowiedź!

Użyj tego, a będziesz bezpieczny i szybki (źródło: moz )

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

Możesz przyjrzeć się skrzypcom porównawczym wydajności i wymuszania typu

Minęło dużo czasu. Dalej jest głównie ze względów historycznych.


Wyniki

Na razie mamy takie rozwiązania:


1. Oczywiste i szybkie

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

1.1. Modyfikacja z kbec - jeden typ rzadziej rzucany, wydajniejszy , krótszy [najszybszy]

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

Uwaga: sign("0") -> 1


2. Eleganckie, krótkie, nie tak szybkie [najwolniejsze]

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

Ostrzeżenie: sign(+-Infinity) -> NaN ,sign("0") -> NaN

Jak Infinityna numer prawny w JS, to rozwiązanie nie wydaje się w pełni poprawne.


3. Sztuka ... ale bardzo wolno [najwolniej]

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

4. Używając szybkiego przesunięcia bitowego
, alesign(-Infinity) -> 0

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

5. Bezpieczne dla typów [megafast]

! Wygląda na to, że przeglądarki (zwłaszcza Chrome v8) dokonują magicznych optymalizacji i to rozwiązanie okazuje się znacznie wydajniejsze niż inne, nawet niż (1.1), mimo że zawiera 2 dodatkowe operacje i logicznie rzecz biorąc, nigdy nie może być szybsze.

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

Przybory

Ulepszenia są mile widziane!


[Offtopic] Zaakceptowana odpowiedź

  • Andrey Tarantsov - +100 za sztukę, ale niestety jest to około 5 razy wolniejsze niż oczywiste podejście

  • Frédéric Hamidi - jakoś najbardziej chwalona odpowiedź (jak na razie piszę) i jest całkiem fajna, ale zdecydowanie nie tak należy to robić, imho. Ponadto nie obsługuje poprawnie liczb nieskończoności, które są również liczbami, wiesz.

  • kbec - to ulepszenie oczywistego rozwiązania. Nie tak rewolucyjne, ale biorąc wszystko razem uważam to podejście za najlepsze. Zagłosuj na niego :)

nieoficjalnych
źródło
3
Chodzi o to, że czasami 0jest to szczególny przypadek
dyskwalifikowany
1
Zrobiłem zestaw testów JSPerf (z różnymi rodzajami danych wejściowych), aby przetestować każdy algorytm, który można znaleźć tutaj: jsperf.com/signs Wyniki mogą nie być takie, jak wymienione w tym poście!
Alba Mendez
2
@ niezadowolony, który z nich? Oczywiście, jeśli uruchomisz test everythingwersję, Safe odmówi przetestowania specjalnych wartości, więc będzie szybszy! only integersZamiast tego spróbuj uruchomić test. Poza tym JSPerf po prostu wykonuje swoją pracę, nie chodzi o to, żeby go lubić. :)
Alba Mendez
2
Według testów jsperf okazuje się, że działa to jak typeof x === "number"magia. Proszę, zrób więcej przebiegów, zwłaszcza FF, Opera i IE, aby było jasne.
zdyskwalifikowany
4
Dla kompletności dodałem nowy test jsperf.com/signs/7 dla Math.sign()(0 === 0, nie tak szybki jak "Safe"), który pojawił się w FF25 i nadchodzi w chrome.
Alex K.

Odpowiedzi:

78

Bardziej elegancka wersja szybkiego rozwiązania:

var sign = number?number<0?-1:1:0
kbec
źródło
5
-1 za połączenie trójskładnikavar sign = (number)? ((number < 0)? -1 : 1 ) : 0
Patrick Michaelsen
3
Math.sign(number)
Илья Зеленько
28

Dzielenie liczby przez jej wartość bezwzględną również daje jej znak. Korzystanie ze zwierającego operatora logicznego AND pozwala nam na specjalne wielkości, 0więc nie kończymy na dzieleniu przez to:

var sign = number && number / Math.abs(number);
Frédéric Hamidi
źródło
6
Prawdopodobnie chciałbyś var sign = number && number / Math.abs(number);na wszelki wypadeknumber = 0
NullUserException
@NullUserException, masz absolutną rację, 0musi mieć specjalną wielkość liter . Odpowiednio zaktualizowano odpowiedź. Dzięki :)
Frédéric Hamidi
Na razie jesteś najlepszy. Ale mam nadzieję, że w przyszłości będzie więcej odpowiedzi.
disfated
24

Funkcja, której szukasz, nazywa się signum , a najlepszym sposobem jej wdrożenia jest:

function sgn(x) {
  return (x > 0) - (x < 0);
}
Andrey Tarantsov
źródło
3
Czekać. Jest błąd: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); daje [-1, -1, -1, 0, 1] for (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); daje poprawne [-1, -1, 0, 1, 1]
odrzucone
13

Czy to nie powinno obsługiwać zer podpisanych w JavaScript (ECMAScript)? Wydaje się, że działa przy zwracaniu x zamiast 0 w funkcji „megafast”:

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

To sprawia, że ​​jest kompatybilny z wersją roboczą Math.sign ( MDN ) ECMAScript :

Zwraca znak x, wskazując, czy x jest dodatnie, ujemne czy zerowe.

  • Jeśli x jest NaN, wynikiem jest NaN.
  • Jeśli x wynosi −0, wynikiem jest −0.
  • Jeśli x wynosi +0, wynikiem jest +0.
  • Jeśli x jest ujemne, a nie −0, wynikiem jest −1.
  • Jeśli x jest dodatnie, a nie +0, wynikiem jest +1.
Martijn
źródło
Niesamowicie szybki i ciekawy mechanizm, jestem pod wrażeniem. Czekam na więcej testów.
kbec
10

Dla osób, które są zainteresowane tym, co się dzieje z najnowszymi przeglądarkami, w wersji ES6 dostępna jest natywna metoda Math.sign . Wsparcie możesz sprawdzić tutaj .

W zasadzie to wraca -1, 1, 0lubNaN

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 Dali
źródło
4
var sign = number >> 31 | -number >>> 31;

Superszybki, jeśli nie potrzebujesz Infinity i wiesz, że liczba jest liczbą całkowitą, znalezioną w źródle openjdk-7: java.lang.Integer.signum()

Toxiro
źródło
1
Nie udaje się to dla małych ujemnych ułamków, takich jak -0,5. (Wygląda na to, że źródło pochodzi z implementacji dla
liczb
1

Pomyślałem, że dodam to dla zabawy:

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

0 i NaN zwróci -1
działa dobrze na +/- Nieskończoność

jaya
źródło
1

Rozwiązanie, które działa na wszystkich liczbach, a także 0i -0, a także Infinityi -Infinity, to:

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

Zobacz pytanie „ Czy +0 i -0 to to samo? ”, Aby uzyskać więcej informacji.


Ostrzeżenie: Żadna z tych odpowiedzi, w tym obecnie standardowym Math.signpraca będzie na razie 0vs -0. Może to nie stanowić problemu dla Ciebie, ale w niektórych implementacjach fizyki może to mieć znaczenie.

Andy Ray
źródło
0

Możesz przesunąć nieco liczbę i sprawdzić najbardziej znaczący bit (MSB). Jeśli MSB jest 1, to liczba jest ujemna. Jeśli jest 0, to liczba jest dodatnia (lub 0).

Brombomb
źródło
@ NullUserException Wciąż mogę się mylić, ale z mojego odczytu „Operandy wszystkich operatorów bitowych są konwertowane na 32-bitowe liczby całkowite ze znakiem w kolejności big-endian i w formacie uzupełnienia do dwóch”. zaczerpnięte z MDN
Brombomb
Wydaje się, że to wciąż bardzo dużo pracy; nadal musisz przekonwertować 1 i 0 na -1 i 1, a 0 również musi zostać uwzględnione. Gdyby OP właśnie tego chciał, byłoby łatwiej po prostu użyćvar sign = number < 0 : 1 : 0
NullUserException
+1. Nie musisz się jednak przesuwać, możesz po prostu zrobić n & 0x80000000maskę bitową. Co do konwersji na 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
davin
@davin Czy wszystkie liczby będą działać z tą maską bitową? Podłączyłem się -5e32i się zepsuł.
NullUserException,
@NullUserException ఠ_ఠ, liczby, które mają ten sam znak po zastosowaniu standardów ToInt32. Jeśli tam przeczytasz (rozdział 9.5), istnieje moduł, który wpływa na wartość liczb, ponieważ zakres 32-bitowej liczby całkowitej jest mniejszy niż zakres typu js Number. Więc to nie zadziała dla tych wartości lub nieskończoności. Jednak nadal podoba mi się odpowiedź.
davin
0

Właśnie miałem zadać to samo pytanie, ale znalazłem rozwiązanie, zanim skończyłem pisać, zobaczyłem, że to pytanie już istnieje, ale nie widziałem tego rozwiązania.

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

wydaje się być szybszy po dodaniu trójskładnika (n >> 31) + (n>0?1:0)

Moritz Roessler
źródło
Bardzo dobrze. Twój kod wydaje się nieco szybszy niż (1). (n> 0? 1: 0) jest szybsze z powodu braku rzutowania. Jedynym rozczarowującym momentem jest znak (-Infinity) daje 0. Zaktualizowane testy.
disfated
0

Bardzo podobne do odpowiedzi Martijna

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

Uważam to za bardziej czytelne. Ponadto (lub, w zależności od twojego punktu widzenia, jednakże), określa również rzeczy, które można zinterpretować jako liczbę; np. wraca -1po wyświetleniu '-5'.

equaeghe
źródło
0

Nie widzę praktycznego sensu zwracania -0 i 0 z, Math.signwięc moja wersja to:

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
źródło
To nie jest funkcja signum
zdyskwalifikowana
0

Metody, które znam, są następujące:

Math.sign (n)

var s = Math.sign(n)

Jest to funkcja natywna, ale jest najwolniejsza ze wszystkich ze względu na narzut wywołania funkcji. Obsługuje jednak „NaN”, gdzie pozostałe poniżej mogą po prostu przyjąć 0 (tj. Math.sign („abc”) to NaN).

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

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

W tym przypadku tylko lewa lub prawa strona może być 1 na podstawie znaku. Powoduje to 1-0(1), 0-1(-1) lub 0-0(0).

Szybkość tego wydaje się łeb w łeb z następną poniżej w Chrome.

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

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

Używa "przesunięcia w prawo propagującego znak". Zasadniczo przesunięcie o 31 zrzuca wszystkie bity z wyjątkiem znaku. Jeśli znak został ustawiony, daje to -1, w przeciwnym razie 0. Po prawej stronie |testuje wartość dodatnią, konwertując wartość na wartość logiczną (0 lub 1 [BTW: łańcuchy nienumeryczne, np.!!'abc' , stają się w tym przypadku 0 i not NaN]) następnie używa bitowej operacji OR do łączenia bitów.

Wydaje się, że jest to najlepsza średnia wydajność we wszystkich przeglądarkach (najlepsza przynajmniej w Chrome i Firefox), ale nie najszybsza we WSZYSTKICH z nich. Z jakiegoś powodu operator trójskładnikowy jest szybszy w IE.

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

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

Z jakiegoś powodu najszybsza w IE.

jsPerf

Wykonane testy: https://jsperf.com/get-sign-from-value

Jamesa Wilkinsa
źródło
0

Moje dwa centy, z funkcją, która zwraca te same wyniki co Math.sign, tj. Sign (-0) -> -0, sign (-Infinity) -> -Infinity, sign (null) -> 0 , znak (nieokreślony) -> NaN itp.

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

Jsperf nie pozwala mi stworzyć testu ani wersji, przepraszam, że nie mogę dostarczyć Ci testów (wypróbowałem jsbench.github.io, ale wyniki wydają się być znacznie bliżej siebie niż w przypadku Jsperf ...)

Gdyby ktoś mógł dodać to do wersji Jsperf, byłbym ciekawy, jak wypada na tle wszystkich podanych wcześniej rozwiązań ...

Dziękuję Ci!

Jim.

EDYCJA :

Powinienem był napisać:

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

( (+x && -1)zamiast (x && -1)) w celu sign('abc')poprawnej obsługi (-> NaN)

Jimshell
źródło
0

Math.sign nie jest obsługiwany w IE 11. Łączę najlepszą odpowiedź z odpowiedzią Math.sign:

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

Teraz można bezpośrednio korzystać z Math.sign.

sudip
źródło
1
Popchnąłeś mnie, żebym zaktualizował moje pytanie. Minęło 8 lat, odkąd został zapytany. Zaktualizowałem również moje jsfiddle do es6 i api window.performance. Ale wolę wersję Mozilli jako polyfill, ponieważ pasuje do wymuszania typu Math.sign. Wydajność nie jest obecnie problemem.
zdyskwalifikowany