Number.sign () у javascript


101

Цікаво, чи існують якісь нетривіальні способи пошуку ознаки числа ( функція signum )?
Можуть бути коротшими / швидшими / більш елегантними рішеннями, ніж очевидні

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

Коротка відповідь!

Використовуйте це, і ви будете безпечні та швидкі (джерело: moz )

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

Ви можете подивитися на продуктивність і типу примушувати порівняння скрипки

Давно минув час. Далі йде переважно з історичних причин.


Результати

На даний момент у нас є такі рішення:


1. Очевидний і швидкий

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

1.1. Модифікація від kbec - один тип: менший, ефективніший, коротший [найшвидший]

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

обережність: sign("0") -> 1


2. Елегантний, короткий, не такий швидкий [найповільніший]

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

застерігають: sign(+-Infinity) -> NaN ,sign("0") -> NaN

Так Infinityяк юридичний номер у JS, це рішення не здається повністю коректним.


3. Мистецтво ... але дуже повільне [найповільніше]

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

4. Використання біт-зсуву
швидко, алеsign(-Infinity) -> 0

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

5. Безпечний тип [мегафаст]

! Схоже, що браузери (особливо хром v8) роблять деякі магічні оптимізації, і це рішення виявляється набагато ефективнішим, ніж інші, навіть ніж (1.1), незважаючи на те, що він містить 2 додаткові операції, і логічно ніколи не може бути швидшим.

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

Інструменти

  • jsperf тести на відповідність;
  • скрипка - тестування на тип;

Поліпшення вітаються!


[Offtopic] Прийнята відповідь

  • Андрій Таранцов - +100 для мистецтва, але, на жаль, це приблизно в 5 разів повільніше, ніж очевидний підхід

  • Фредерік Хаміді - якось найбільш схвалена відповідь (на той час написана), і це якось круто, але це точно не так, як слід робити, імхо. Крім того, він неправильно обробляє числа нескінченності, які також є числами.

  • kbec - це вдосконалення очевидного рішення. Не такий революційний, але, приймаючи всіх разом, я вважаю цей підхід найкращим. Проголосуйте за нього :)


3
справа в тому, що іноді 0це особливий випадок
зневірений

1
Я зробив набір тестів JSPerf (з різними видами введення), щоб перевірити кожен алгоритм, який можна знайти тут: jsperf.com/signs Результати можуть бути не такими, як зазначено в цій публікації!
Альба Мендес

2
@disfated, хто з них? Звичайно, якщо ви запустите test everythingверсію, Safe відмовиться перевіряти спеціальні значення, так що це буде швидше! Спробуйте only integersзамість цього запустити тест. Крім того, JSPerf просто виконує свою роботу, це не питання вподобання. :)
Альба Мендес

2
Згідно з тестами jsperf, виявляється, що це typeof x === "number"робить магію перформансу. Будь ласка, зробіть більше запусків, особливо FF, Opera та IE, щоб це було зрозуміло.
неспокійно

4
Для повноти я додав новий тест jsperf.com/signs/7 для Math.sign()(0 === 0, не такий швидкий, як "Безпечний"), який з'явився у FF25 і має бути в хромі.
Олексій К.

Відповіді:



28

Ділення числа на його абсолютне значення також дає його знак. Використання короткого замикання логічного оператора AND дозволяє нам робити особливий випадок, 0тому ми не закінчуємо його поділом:

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

6
Ви, мабуть, захочете var sign = number && number / Math.abs(number);у випадкуnumber = 0
NullUserException

@NullUserException, ви абсолютно праві, 0потребуєте спеціальних обставин. Відповідь оновлена ​​відповідно. Дякую :)
Фредерік Хаміді

Ти зараз найкращий. Але я сподіваюся, що відповідей у ​​майбутньому буде більше.
зневірився

24

Функція, яку ви шукаєте, називається signum , і найкращий спосіб її реалізації:

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

3
Зачекайте. Є помилка: for (x = -2; x <= 2; x ++) console.log ((x> 1) - (x <1)); дає [-1, -1, -1, 0, 1] для (x = -2; x <= 2; x ++) console.log ((x> 0) - (x <0)); дає правильно [-1, -1, 0, 1, 1]
зневірено

13

Якщо це не підтримує підписані нулі JavaScript (ECMAScript)? Здається, це працює при поверненні x, а не 0 у функції "megafast":

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

Це робить його сумісним з проектом Math.sign ( MDN ) ECMAScript :

Повертає знак x, вказуючи, чи x додатний, негативний чи нульовий.

  • Якщо x - NaN, результат - NaN.
  • Якщо x дорівнює −0, результат −0.
  • Якщо х +0, результат +0.
  • Якщо x негативний, а не –0, результат −1.
  • Якщо х додатний, а не +0, результат +1.

Неймовірно швидкий і цікавий механізм, я вражений. Чекаю ще тестів.
kbec

10

Для людей, які цікавляться тим, що відбувається з останніми браузерами, у версії ES6 є власний метод Math.sign . Ви можете перевірити підтримку тут .

В основному він повертається -1, 1, 0абоNaN

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;

Супершвидкий, якщо вам не потрібен Infinity і знаєте, що число є цілим числом, знайдене у джерелі openjdk-7: java.lang.Integer.signum()


1
Це не вдається для малих негативних фракцій, таких як -0,5. (Схоже, джерело - це реалізація, зокрема для
Integers

1

Я думав, що додам це просто заради задоволення:

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

0 і NaN поверне -1
працює добре на +/- Нескінченність


1

Рішення , яке працює на всі числа, а також 0і -0, так само як Infinityі -Infinity, є:

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

Див. Питання " Чи +0 і -0 однаково? " Для отримання додаткової інформації.


Попередження: Жоден із цих відповідей, включаючи стандарт, що зараз Math.signпрацює, не буде працювати у справі 0проти -0. Це може не бути проблемою для вас, але в деяких фізичних реалізаціях це може мати значення.


0

Ви можете трохи змінити номер і перевірити найважливіший біт (MSB). Якщо MSB дорівнює 1, то число від’ємне. Якщо це 0, то число додатне (або 0).


@ NullUserException Я все-таки можу помилятися, але з мого читання "Операнди всіх побітових операторів перетворюються на підписані 32-бітні цілі числа в порядку великого ендіану та у форматі доповнення двох". взяті з MDN
Brombomb

Це все ще здається жахливою роботою; ви все ще повинні перетворити 1 і 0 в -1 і 1, і 0 також слід подбати. Якби ОП просто цього хотіла, було б простіше просто використовуватиvar sign = number < 0 : 1 : 0
NullUserException

+1. Немає потреби змінюватись, ви можете просто так, n & 0x80000000як битмаска. Щодо перетворення на 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
Давін

@davin Чи всі номери гарантовано працювати з цією растровою маскою? Я підключився -5e32і він зламався.
NullUserException

@NullUserException ఠ_ఠ, числа, які мають однаковий знак при застосуванні стандартів ToInt32. Якщо ви читаєте там (розділ 9.5), існує модуль, який впливає на значення чисел, оскільки діапазон 32-бітного цілого числа менше діапазону типу js Число. Таким чином, це не буде працювати для цих цінностей, або нескінченності. Мені все одно подобається відповідь.
Давін

0

Я щойно збирався задати те саме питання, але прийшов до рішення, перш ніж я закінчив писати, побачив, що це питання вже існує, але не бачив цього рішення.

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

здається, що це швидше, додавши все-таки потрійне (n >> 31) + (n>0?1:0)


Дуже хороша. Ваш код здається трохи швидшим, ніж (1). (n> 0? 1: 0) швидше через відсутність передачі типів. Єдиний дихаючий момент - знак (-Безмежність) дає 0. Оновлені тести.
зневірився

0

Дуже схожа на відповідь Мартійна

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

Я вважаю це більш читабельним. Також (або, однак, залежно від вашої точки зору), він також впорядковує речі, які можна інтерпретувати як число; наприклад, він повертається -1при поданні з '-5'.


0

Я не бачу жодної практичної сенси повернення -0 і 0 від Math.signмоєї версії:

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

Це не функція signum
зірвана

0

Мені відомі методи:

Math.sign (n)

var s = Math.sign(n)

Це основна функція, але найповільніша з усіх через накладні витрати виклику функції. Однак він обробляє 'NaN', коли інші нижче можуть просто вважати 0 (тобто Math.sign ('abc') - NaN).

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

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

У такому випадку лише ліва або права сторона може бути знаком 1 на основі знака. Це призводить до або 1-0(1), 0-1(-1), або 0-0(0).

Швидкість цього виглядає як шия, так і наступна нижче в Chrome.

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

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

Використовується "правильний зсув, що поширюється знаком". В основному зміщення на 31 краплю всіх біт, крім знака. Якщо знак встановлено, це призводить до -1, інакше він дорівнює 0. Право |його тестує на позитивне, перетворюючи значення на булеве (0 або 1 [BTW: нечислові рядки, наприклад !!'abc', стають 0 у цьому випадку, і не NaN]) потім використовує побітну операцію АБО для об'єднання бітів.

Це здається найкращою середньою продуктивністю у веб-переглядачах (найкраща в Chrome та Firefox принаймні), але не найшвидша у ВСІХ. Чомусь потрійний оператор швидше працює в IE.

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

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

Найшвидший в IE чомусь.

jsPerf

Виконані тести: https://jsperf.com/get-sign-from-value


0

Мої два центи, з функцією, яка повертає ті самі результати, що і Math.sign, тобто знак (-0) -> -0, знак (-бесконечність) -> -Безмежність, знак (нуль) -> 0 , знак (невизначений) -> NaN тощо.

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

Jsperf не дозволить мені створити тест чи версію, вибачте за те, що не можу надати вам тести (я постарався jsbench.github.io спробувати, але результати здаються набагато ближче один до одного, ніж з Jsperf ...)

Якщо хтось може, будь ласка, додати його до редакції Jsperf, мені було б цікаво побачити, як він порівнюється з усіма раніше заданими рішеннями ...

Дякую!

Джим.

Редагувати :

Я повинен був написати:

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

( (+x && -1)замість (x && -1)) для sign('abc')правильного поводження (-> NaN)


0

Math.sign не підтримується в IE 11. Я поєдную найкращу відповідь з відповіддю Math.sign:

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

Тепер можна використовувати Math.sign безпосередньо.


1
Ви підштовхнули мене до оновлення мого питання. Минуло 8 років з моменту прохання. Також оновлено мій jsfiddle до es6 та window.performance api. Але я віддаю перевагу версії mozilla як полісистему, оскільки вона відповідає типу примушування Math.sign. Продуктивність сьогодні не викликає особливих проблем.
зневірився
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.