Number.sign () в javascript


101

Интересно, есть ли какие-нибудь нетривиальные способы найти знак числа ( сигнум-функция )?
Может быть короче / быстрее / элегантнее, чем очевидное

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. Безопасный тип [мегабыстрый]

! Похоже, браузеры (особенно chrome v8) делают некоторые волшебные оптимизации, и это решение оказывается намного более производительным, чем другие, даже чем (1.1), несмотря на то, что оно содержит 2 дополнительные операции и, по логике, никогда не может быть быстрее.

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

инструменты

  • предварительные тесты jsperf ;
  • fiddle - тесты типа cast;

Улучшения приветствуются!


[Offtopic] Ответ принят

  • Андрей Таранцов - +100 за искусство, но, к сожалению, это примерно в 5 раз медленнее очевидного подхода.

  • Фредерик Хамиди - ответ, получивший наибольшее количество голосов (на тот момент, когда я писал), и это вроде как круто, но это определенно не то, как надо делать, imho. Кроме того, он неправильно обрабатывает бесконечные числа, которые, как вы знаете, тоже являются числами.

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


3
0Дело в том, что иногда это особый случай
дискредитирован

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

2
@ неудовлетворен, какой из них? Конечно, если вы запустите test everythingверсию, Safe откажется проверять специальные значения, так что это будет быстрее! only integersВместо этого попробуйте запустить тест. Кроме того, JSPerf просто выполняет свою работу, и это не значит, что это нравится. :)
Alba Mendez

2
Согласно тестам jsperf выясняется, что это добавляет typeof x === "number"магии производительности. Пожалуйста, сделайте больше запусков, особенно FF, Opera и IE, чтобы было понятно.
дискредитирован

4
Для полноты картины я добавил новый тест jsperf.com/signs/7 для Math.sign()(0 === 0, не так быстро, как "Safe"), который появился в FF25 и скоро появится в Chrome.
Alex K.

Ответы:


78

Более элегантный вариант быстрого решения:

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

5
-1 за то, что смешал вашу троицу вместеvar sign = (number)? ((number < 0)? -1 : 1 ) : 0
Патрик

3
Math.sign(number)
Илья Зеленько

28

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

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

6
Вы, вероятно, захотите var sign = number && number / Math.abs(number);на number = 0
всякий

@NullUserException, вы абсолютно правы, 0должно быть в специальном регистре. Ответ обновлен соответственно. Спасибо :)
Фредерик Хамиди

На данный момент ты лучший. Но я надеюсь, что в будущем будет больше ответов.
дискредитирован 02

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]
disfated

13

Должно ли это не поддерживать подписанные нули JavaScript (ECMAScript)? Кажется, это работает при возврате x, а не 0 в функции «мегабыстрая»:

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

Это делает его совместимым с черновиком ECMAScript Math.sign ( MDN ):

Возвращает знак x, указывающий, является ли x положительным, отрицательным или нулем.

  • Если x равен NaN, результат равен NaN.
  • Если x равен −0, результат равен −0.
  • Если x равен +0, результат равен +0.
  • Если x отрицательно, а не -0, результат равен -1.
  • Если x положительный, а не +0, результат равен +1.

Невероятно быстрый и интересный механизм, впечатлил. Жду дополнительных тестов.
kbec 07

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. (Похоже, что источник
взят

1

Я думал, что добавлю это просто для удовольствия:

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

0 и NaN вернет -1
отлично работает на +/- Infinity


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 02

Это все еще кажется ужасно большим объемом работы; вам все равно нужно преобразовать 1 и 0 в -1 и 1, и о 0 также нужно позаботиться. Если бы OP просто захотел этого, было бы проще использоватьvar sign = number < 0 : 1 : 0
NullUserException

+1. Не нужно переключаться, вы можете просто сделать n & 0x80000000как битовую маску. Что касается преобразования в 0,1, -1:n && (n & 0x80000000 ? -1 : 1)
Дэвин

@davin Все ли числа гарантированно работают с этой битовой маской? Я подключил, -5e32и он сломался.
NullUserException 02

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

0

Я как раз собирался задать тот же вопрос, но пришел к решению еще до того, как закончил писать, увидел, что этот вопрос уже существует, но не видел этого решения.

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

кажется, что быстрее, если добавить троичный (n >> 31) + (n>0?1:0)


Очень хорошо. Ваш код кажется немного быстрее, чем (1). (n> 0? 1: 0) быстрее из-за отсутствия приведения типа. Единственный разочаровывающий момент - знак (-Infinity) дает 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

Это не
знаковая

0

Я знаю следующие методы:

Math.sign (сущ.)

var s = Math.sign(n)

Это собственная функция, но она работает медленнее всего из-за накладных расходов на вызов функции. Однако он обрабатывает «NaN», тогда как другие ниже могут просто принять 0 (т.е. Math.sign ('abc') равен NaN).

((п> 0) - (п <0))

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

В этом случае только левая или правая сторона может быть 1 в зависимости от знака. Результатом является либо 1-0(1), 0-1(-1), либо 0-0(0).

Скорость этого, похоже, шире, чем у следующего ниже в Chrome.

(п >> 31) | (!! п)

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

Использует «сдвиг вправо с распространением знака». Обычно сдвиг на 31 сбрасывает все биты, кроме знака. Если знак был установлен, это приводит к -1, в противном случае - 0. Справа от |него проверяется положительное значение путем преобразования значения в логическое (0 или 1 [BTW: нечисловые строки, например!!'abc' , в этом случае становятся 0, и not NaN]) затем использует побитовую операцию ИЛИ для объединения битов.

Похоже, это лучшая средняя производительность для всех браузеров (лучшая, по крайней мере, в Chrome и Firefox), но не самая быстрая во ВСЕХ из них. По какой-то причине тернарный оператор в IE работает быстрее.

п? п <0? -1: 1: 0

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

По какой-то причине самый быстрый в IE.

jsPerf

Выполненные тесты: https://jsperf.com/get-sign-from-value


0

Мои два цента с функцией, которая возвращает те же результаты, что и Math.sign, то есть sign (-0) -> -0, sign (-Infinity) -> -Infinity, sign (null) -> 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.