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 :)


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:



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);

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);
}

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.

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

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()


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ść


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.


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).


@ 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)


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'.


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

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


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)


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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.