Существует ли стандартная функция знака (signum, sgn) в C / C ++? - PullRequest
371 голосов
/ 15 декабря 2009

Мне нужна функция, которая возвращает -1 для отрицательных чисел и +1 для положительных чисел. http://en.wikipedia.org/wiki/Sign_function Достаточно легко написать свою собственную, но кажется, что-то, что должно быть где-то в стандартной библиотеке.

Редактировать: В частности, я искал функцию, работающую с плавающей точкой.

Ответы [ 23 ]

464 голосов
/ 06 января 2011

Удивило, что еще никто не опубликовал версию C ++ без веток:

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

Преимущества:

  • На самом деле реализует signum (-1, 0 или 1). Реализации здесь, использующие copysign, только возвращают -1 или 1, что не является signum. Кроме того, некоторые реализации здесь возвращают float (или T), а не int, что кажется расточительным.
  • Работает для целых чисел, чисел с плавающей запятой, двойных чисел, шорт без знака или любых пользовательских типов, которые могут быть созданы из целого числа 0 и могут быть заказаны.
  • Быстро! copysign медленно, особенно если вам нужно повысить, а затем снова сузиться. Это не имеет ответвлений и отлично оптимизирует
  • Стандарты-совместимые! Хак с бит-сдвигом аккуратен, но работает только для некоторых битовых представлений и не работает, когда у вас тип без знака. Это может быть предоставлено как ручная специализация, когда это уместно.
  • точный! Простые сравнения с нулем могут поддерживать внутреннее высокоточное представление машины (например, 80 бит на x87) и избежать преждевременного округления до нуля.

Предостережения:

  • Это шаблон, поэтому его компиляция займет вечность.
  • Очевидно, некоторые люди считают более понятным использование новой, несколько эзотерической и очень медленной стандартной библиотечной функции , которая даже не реализует сигнум .
  • Часть проверки < 0 запускает предупреждение GCC -Wtype-limits при создании экземпляра для типа без знака. Вы можете избежать этого, используя некоторые перегрузки:

    template <typename T> inline constexpr
    int signum(T x, std::false_type is_signed) {
        return T(0) < x;
    }
    
    template <typename T> inline constexpr
    int signum(T x, std::true_type is_signed) {
        return (T(0) < x) - (x < T(0));
    }
    
    template <typename T> inline constexpr
    int signum(T x) {
        return signum(x, std::is_signed<T>());
    }
    

    (Это хороший пример первого предупреждения.)

258 голосов
/ 15 декабря 2009

Я не знаю стандартной функции для этого. Вот интересный способ написать это:

(x > 0) - (x < 0)

Вот более удобный способ сделать это:

if (x > 0) return 1;
if (x < 0) return -1;
return 0;

Если вам нравится троичный оператор, вы можете сделать это:

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
181 голосов
/ 15 декабря 2009

Существует математическая функция библиотеки C99, называемая copysign (), которая получает знак одного аргумента и абсолютное значение другого:

result = copysign(1.0, value) // double
result = copysignf(1.0, value) // float
result = copysignl(1.0, value) // long double

даст вам результат +/- 1,0, в зависимости от знака значения. Обратите внимание, что нули с плавающей запятой подписаны: (+0) даст +1, а (-0) даст -1.

73 голосов
/ 13 апреля 2012

Судя по всему, ответ на вопрос автора оригинала - нет. Нет стандартной C ++ sgn функции.

72 голосов
/ 01 июня 2013

Похоже, что в большинстве ответов пропущен исходный вопрос.

Существует ли стандартная функция знака (signum, sgn) в C / C ++?

Нет в стандартной библиотеке, однако есть copysign, который можно использовать почти так же, как через copysign(1.0, arg), и в boost есть функция истинного знака , что также может быть частью стандарта.

    #include <boost/math/special_functions/sign.hpp>

    //Returns 1 if x > 0, -1 if x < 0, and 0 if x is zero.
    template <class T>
    inline int sign (const T& z);

http://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/utils/sign_functions.html

28 голосов
/ 18 августа 2011

Быстрее, чем вышеперечисленные решения, включая решение с самым высоким рейтингом:

(x < 0) ? -1 : (x > 0)
21 голосов
/ 18 февраля 2016

Есть ли стандартная функция знака (signum, sgn) в C / C ++?

Да, в зависимости от определения.

C99 и более поздние версии имеют макрос signbit() в <math.h>

int signbit (реальный плавающий x);
Макрос signbit возвращает ненулевое значение тогда и только тогда, когда знак его значения аргумента отрицателен. C11 §7.12.3.6


И все же ОП хочет что-то немного другое.

Мне нужна функция, которая возвращает -1 для отрицательных чисел и +1 для положительных чисел. ... функция, работающая с плавающей точкой.

#define signbit_p1_or_n1(x)  ((signbit(x) ?  -1 : 1)

Глубже:

Пост не является конкретным в следующих случаях, x = 0.0, -0.0, +NaN, -NaN.

Классический signum() возвращает +1 в x>0, -1 в x>0 и 0 в x==0.

Многие ответы уже охватили это, но не относятся к x = -0.0, +NaN, -NaN. Многие из них ориентированы на целочисленную точку зрения, в которой обычно отсутствуют не-числа ( NaN ) и -0.0 .

Типичные ответы работают как signnum_typical() Вкл. -0.0, +NaN, -NaN, они возвращают 0.0, 0.0, 0.0.

int signnum_typical(double x) {
  if (x > 0.0) return 1;
  if (x < 0.0) return -1;
  return 0;
}

Вместо этого предложите эту функциональность: при -0.0, +NaN, -NaN возвращается -0.0, +NaN, -NaN.

double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}
16 голосов
/ 15 декабря 2009

Есть способ сделать это без ветвления, но это не очень красиво.

sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

http://graphics.stanford.edu/~seander/bithacks.html

Множество других интересных, слишком умных вещей на этой странице тоже ...

11 голосов
/ 15 декабря 2009

Если все, что вам нужно, это проверить знак, используйте signbit (возвращает true, если его аргумент имеет отрицательный знак). Не уверен, почему вы хотели бы вернуть -1 или +1; Copysign удобнее для этого, но похоже, что он вернет +1 для отрицательного нуля на некоторых платформах с только частичная поддержка отрицательного нуля, где signbit предположительно вернул бы true.

5 голосов
/ 26 мая 2015

Как правило, в C / C ++ нет стандартной функции signum, и отсутствие такой фундаментальной функции многое говорит вам об этих языках.

Кроме того, я полагаю, что обе точки зрения большинства о правильном подходе к определению такой функции в некотором смысле верны, и «спор» об этом фактически не является аргументом, если принять во внимание два важных предостережения:

  • Функция signum всегда должна возвращать тип своего операнда, аналогично функции abs(), поскольку signum обычно используется для умножения с абсолютным значением после последний был обработан как-то. Поэтому основной вариант использования signum - это не сравнение, а арифметика, и последний не должен включать дорогостоящие преобразования целых чисел в / из плавающей запятой.

  • Типы с плавающей запятой не имеют единого точного нулевого значения: +0.0 можно интерпретировать как «бесконечно меньше нуля», а -0.0 - как «бесконечно меньше нуля». По этой причине сравнения, включающие ноль, должны внутренне сверяться с обоими значениями, а выражение типа x == 0.0 может быть опасным.

Что касается C, я думаю, что лучший способ продвинуться с интегральными типами - это действительно использовать выражение (x > 0) - (x < 0), так как оно должно быть переведено без ветвления и требует только трех основных операций. Лучше всего определить встроенные функции, которые обеспечивают возвращаемый тип, соответствующий типу аргумента, и добавить C11 define _Generic, чтобы сопоставить эти функции общему имени.

Я думаю, что при использовании значений с плавающей запятой встроенные функции, основанные на C11 copysignf(1.0f, x), copysign(1.0, x) и copysignl(1.0l, x), - просто потому, что они также с большой вероятностью не имеют ветвей и дополнительно делают не требует приведения результата из целого числа обратно в значение с плавающей запятой. Вам, вероятно, следует заметить, что ваши реализации с плавающей запятой signum не будут возвращать ноль из-за особенностей значений с плавающей запятой, соображений времени обработки, а также потому, что это часто очень полезно в арифметике с плавающей запятой для получения правильный знак -1 / + 1, даже для нулевых значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...