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


409

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

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


13
Что он должен вернуть за 0?
Крейг МакКуин

61
@Craig McQueen; это зависит от того, положительный ноль или отрицательный ноль.
ysth

1
Я заметил, что вы указали возвращаемое значение как целое число. Вы ищете решение, которое принимает целые числа или числа с плавающей запятой?
Марк Байерс

6
@ysth @ Крейг МакКуин, тоже не подходит для поплавков, нет? Определение sgn (x) говорит, что должно возвращать 0, если x==0. Согласно IEEE 754 отрицательный ноль и положительный ноль должны сравниваться как равные.
RJFalconer

5
@ysth "это зависит от положительного нуля или отрицательного нуля". На самом деле это не так.
RJFalconer

Ответы:


507

Удивило, что никто еще не опубликовал типобезопасную версию 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) и избежать преждевременного округления до нуля.

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

  • Это шаблон, поэтому в некоторых случаях сборка может занять больше времени.
  • Очевидно, некоторые люди считают более понятным использование новой, несколько эзотерической и очень медленной стандартной библиотечной функции , которая даже не реализует Signum .
  • < 0Часть проверки триггеров ССЗ -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>());
    }

    (Что является хорошим примером первого предупреждения.)


18
@GMan: GCC только сейчас (4.5) перестал иметь квадратичную стоимость по отношению к количеству экземпляров для шаблонных функций, и они по-прежнему значительно дороже для анализа и создания экземпляров, чем написанные вручную функции или стандартный препроцессор Си. Компоновщик также должен проделать дополнительную работу по удалению дублирующихся экземпляров. Шаблоны также поощряют # include-in-# include, что делает вычисление зависимостей более длительным и небольшими (часто реализация, а не интерфейс) изменениями, заставляющими перекомпилировать больше файлов.

15
@Joe: Да, и до сих пор нет заметных затрат. C ++ использует шаблоны, это то, что мы все должны понять, принять и преодолеть.
GManNickG

42
Подожди, что это за "медленный" бизнес? Использование текущих компиляторов (g ++ 4.6+, clang ++ 3.0), std::copysignкажется, дает мне отличный код: 4 инструкции (встроенные), без ветвления, полностью с использованием FPU. Рецепт, приведенный в этом ответе, напротив, генерирует гораздо худший код (гораздо больше инструкций, включая умножение, перемещение назад и вперед между целочисленными единицами и FPU) ...
snogglethorpe

14
@snogglethorpe: Если вы вызываете copysignint, он продвигает float / double и должен снова сужаться при возврате. Ваш компилятор может оптимизировать эту рекламу, но я не могу найти ничего, что могло бы гарантировать стандарт. Также для реализации signum через copysign вам нужно вручную обработать регистр 0 - пожалуйста, убедитесь, что вы включили это в любое сравнение производительности.

53
Первая версия не ветвится. Почему люди думают, что сравнение, используемое в выражении, не будет генерировать ветвь? Это будет на большинстве архитектур. Только процессоры, которые имеют cmove (или предикацию), будут генерировать код без ответвлений, но они будут делать это также для троичных файлов или если / иначе, если это победа.
Патрик Шлютер

271

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

(x > 0) - (x < 0)

Вот более читаемый способ сделать это:

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

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

(x > 0) ? 1 : ((x < 0) ? -1 : 0)

7
Марк Рэнсом, ваши выражения дают неверные результаты для x==0.
Авакар

3
@Svante: «Каждый из операторов <, >... сдаст 1 , если указанное соотношение справедливо и 0 , если оно ложно»
Стивен Canon

11
@Svante: не совсем так. Значение 0«ложь»; любое другое значение "true"; однако операторы отношения и равенства всегда возвращают 0или 1(см. Стандарты 6.5.8 и 6.5.9). - значение выражения a * (x == 42)является либо 0или a.
pmg

21
Высокая производительность Марк, я поражен, что вы пропустили тег C ++. Этот ответ очень действителен и не заслуживает повторного голосования. Более того, я бы не стал использовать copysignдля интеграла, xдаже если бы он был у меня в наличии.
Авакар

6
Кто-нибудь проверял, какой код GCC / G ++ / любой другой компилятор испускает на реальной платформе? Я предполагаю, что версия «без ветвей» использует две ветви вместо одной. Bitshifting, вероятно, намного быстрее - и более портативен с точки зрения производительности.
Йорген Фог

192

Существует функция математической библиотеки 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.


57
Проголосовал этот, самый популярный ответ. Оставленный потрясенным изумлением, что SO-сообщество, похоже, предпочитает использовать стандартную библиотечную функцию. Пусть боги программирования обрекают вас всех на попытки расшифровки хаков, используемых умными программистами, незнакомыми с языковыми стандартами. Да, я знаю, что это обойдется мне в тонну повторения на SO, но я бы предпочел наступающий шторм, чем все остальные ...
High Performance Mark

34
Это близко, но это дает неправильный ответ для нуля (по крайней мере, согласно статье Википедии в вопросе). Хорошее предложение, хотя. +1 в любом случае.
Марк Байерс

4
Если вам нужно целое число, или если вы хотите получить точный результат для нулей, мне нравится ответ Марка Байерса, который изящно изящен! Если вас не волнует вышесказанное, функция copysign () может иметь преимущество в производительности, в зависимости от приложения - если бы я оптимизировал критический цикл, я бы попробовал оба варианта.
Грядущая буря

10
1) C99 не везде поддерживается полностью (рассмотрим VC ++); 2) это тоже вопрос C ++. Это хороший ответ, но одобренный также работает и более широко применим.
Павел Минаев

5
Спаситель! Нужен был способ определения между -0,0 и 0,0
Олафур Вааге

79

Кажется, что большинство ответов пропустили оригинальный вопрос.

Существует ли стандартная функция знака (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


5
Это должен быть ответ с наибольшим количеством голосов, так как он дает максимально возможное решение того, что задано в вопросе.
BartoszKP

Последние несколько минут я удивлялся, почему в стандартной библиотеке нет функции подписи. Это просто так - определенно чаще используется, чем гамма-функция, которую можно найти в заголовке cmath.
Таоцзы

4
Объяснение, которое я часто получаю для подобных вопросов, - это «достаточно легко реализовать себя», что не является веской причиной для ИМО. Это полностью противоречит проблемам того, где стандартизация, неочевидные крайние случаи и где поместить такой широко используемый инструмент.
Catskul

77

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


2
@SR_ Вы не правы. copysign()не сделает ваш первый параметр 0.0, если второй 0.0. Другими словами, Джон прав.
Алексис Вилке

30

Существует ли стандартная функция знака (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() On -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;
}

1
Ах, именно то, что я после. Это просто изменилось в Pharo Smalltalk github.com/pharo-project/pharo/pull/1835, и я подумал, существует ли какой-то стандарт (IEC 60559 или ISO 10967), диктующий поведение для отрицательного нуля и поведения нана ... Мне нравится javascript sign developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
aka.nice

29

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

(x < 0) ? -1 : (x > 0)

1
Какой тип х? Или вы используете #define?
Шанс

3
Ваш тип не быстрее. Это часто приводит к отсутствию кэша.
Джеффри Дрейк

19
Кеш пропустить? Я не уверен как. Возможно, вы имели в виду ошибочное прогнозирование отрасли?
Catskul

2
Мне кажется, это приведет к предупреждению о путанице целочисленных и логических типов!
Сергиол

как это будет быстро с веткой?
Ник

16

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

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

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

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


1
Если я правильно прочитал ссылку, она возвращает только -1 или 0. Если вы хотите -1, 0 или +1, то это sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));или sign = (v > 0) - (v < 0);.
Z бозон

1
это означает, что vэто целочисленный тип, не шире, чем int
phuclv

12

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


7
Есть много математических приложений, в которых знак (х) необходим. В противном случае я бы просто сделал if (x < 0).
Шанс

5

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

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

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

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

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

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


5

Моя копия C в двух словах показывает существование стандартной функции copysign, которая может быть полезна. Похоже, что copysign (1.0, -2.0) вернет -1.0, а copysign (1.0, 2.0) вернет +1.0.

Довольно близко, а?


Не стандартно, но может быть широко доступно. Microsoft начинает с подчеркивания, которое они используют для нестандартных расширений. Однако не лучший выбор, когда вы работаете с целыми числами.
Марк Рэнсом

5
copysign соответствует стандартам ISO C (C99) и POSIX. См opengroup.org/onlinepubs/000095399/functions/copysign.html
LHF

3
Что LHF сказал. Visual Studio не является эталоном для стандарта C.
Стивен Кэнон

3

Нет, его нет в c ++, как в matlab. Я использую макрос в моих программах для этого.

#define sign(a) ( ( (a) < 0 )  ?  -1   : ( (a) > 0 ) )

5
Надо отдавать предпочтение шаблонам над макросами в C ++.
Руслан

В Си нет шаблона ...... helloacm.com/how-to-implement-the-sgn-function-in-c
doctorlai

Я подумал, что это хороший ответ, затем я посмотрел на свой собственный код и обнаружил, #define sign(x) (((x) > 0) - ((x) < 0))что это тоже хорошо.
Мишель Рузич

1
встроенная функция лучше, чем макрос в C, а в C ++ шаблон лучше
phuclv

3

Принятый ответ с приведенной ниже перегрузкой действительно не вызывает -Wtype-limit .

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

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

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

Для C ++ 11 альтернативой может быть.

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, int>::type
inline constexpr signum(T const x) {
    return T(0) < x;  
}

template <typename T>
typename std::enable_if<std::is_signed<T>::value, int>::type
inline constexpr signum(T const x) {
    return (T(0) < x) - (x < T(0));  
}

Для меня это не вызывает никаких предупреждений на GCC 5.3.1.


Чтобы избежать -Wunused-parameterпредупреждения, просто используйте неназванные параметры.
Джонатан

Это на самом деле очень верно. Я пропустил это. Однако мне больше нравится альтернатива C ++ 11.
SamVanDonut

2

Немного не по теме, но я использую это:

template<typename T>
constexpr int sgn(const T &a, const T &b) noexcept{
    return (a > b) - (a < b);
}

template<typename T>
constexpr int sgn(const T &a) noexcept{
    return sgn(a, T(0));
}

и я обнаружил, что первая функция - с двумя аргументами - гораздо более полезна из «стандартного» sgn (), потому что она чаще всего используется в коде, подобном следующему:

int comp(unsigned a, unsigned b){
   return sgn( int(a) - int(b) );
}

против

int comp(unsigned a, unsigned b){
   return sgn(a, b);
}

здесь нет броска для неподписанных типов и дополнительного минуса.

на самом деле у меня есть этот кусок кода с помощью sgn ()

template <class T>
int comp(const T &a, const T &b){
    log__("all");
    if (a < b)
        return -1;

    if (a > b)
        return +1;

    return 0;
}

inline int comp(int const a, int const b){
    log__("int");
    return a - b;
}

inline int comp(long int const a, long int const b){
    log__("long");
    return sgn(a, b);
}

1

Вопрос старый, но теперь есть такая желаемая функция. Я добавил обертку с not, left shift и dec.

Вы можете использовать функцию-оболочку на основе signbit из C99 , чтобы получить точное желаемое поведение (см. Код ниже).

Возвращает, является ли знак х отрицательным.
Это также может быть применено к бесконечности, NaN и нулям (если ноль без знака, он считается положительным

#include <math.h>

int signValue(float a) {
    return ((!signbit(a)) << 1) - 1;
}

NB: я использую операнд не ("!"), Потому что возвращаемое значение signbit не указано равным 1 (хотя примеры позволяют нам думать, что так будет всегда), но верно для отрицательного числа:

Возвращаемое значение
Ненулевое значение (true), если знак x отрицательный; и ноль (ложь) в противном случае.

Затем я умножаю на два с левым смещением («<< 1»), что даст нам 2 для положительного числа и 0 для отрицательного и, наконец, уменьшу на 1, чтобы получить 1 и -1 для соответственно положительных и отрицательных чисел, как этого требует ОП.


0 тоже будет положительным ... что может или не может быть тем, чего хотел ОП ...
Антти Хаапала

ну, мы никогда не узнаем, чего действительно хотел OP, если n = 0 ...!
Антонин ГАВРЕЛЬ

0

Хотя целочисленное решение в принятом ответе довольно элегантно, меня беспокоило, что оно не сможет вернуть NAN для двойных типов, поэтому я немного его изменил.

template <typename T> double sgn(T val) {
    return double((T(0) < val) - (val < T(0)))/(val == val);
}

Обратите внимание , что возвращение с плавающей точкой NAN , в отличии от жестких закодированных NANпричин знакового бита быть установлены в некоторых реализациях , поэтому выход для val = -NANи val = NANсобираешься не может быть одинаковым независимо от того , что (если вы предпочитаете « nan» выход больше -nanвы можете положить abs(val)перед возвращением ...)



0

Вот реализация для ветвления:

inline int signum(const double x) {
    if(x == 0) return 0;
    return (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

Если ваши данные не имеют нулей в качестве половины чисел, здесь предиктор ветвлений выберет одну из ветвей в качестве наиболее распространенной. Обе ветви включают только простые операции.

В качестве альтернативы, на некоторых компиляторах и архитектурах ЦП версия без ответвлений может быть быстрее:

inline int signum(const double x) {
    return (x != 0) * 
        (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

Это работает для двоичного формата с плавающей точкой двойной точности IEEE 754: binary64 .


-1
int sign(float n)
{     
  union { float f; std::uint32_t i; } u { n };
  return 1 - ((u.i >> 31) << 1);
}

Эта функция предполагает:

  • двоичное32 представление чисел с плавающей точкой
  • компилятор, который делает исключение из правила строгого псевдонима при использовании именованного объединения

3
Здесь все еще есть некоторые плохие предположения. Например, я не верю, что порядковый номер типа float гарантированно является порядковым номером целого числа. Ваша проверка также не проходит на любой архитектуре, использующей ILP64. На самом деле, вы просто реализуете copysign; если вы используете, static_assertу вас есть C ++ 11, и вы можете использовать его copysign.


-3

Зачем использовать троичные операторы и если-иначе, когда вы можете просто сделать это

#define sgn(x) x==0 ? 0 : x/abs(x)

3
Ваше определение также использует троичный оператор.
Мартин Р

Да Определенно, но он просто использует один троичный оператор для разделения нулевых и ненулевых чисел. Версии других включают вложенные троичные операции для разделения положительного, отрицательного и нулевого.
Jagreet

Использование целочисленного деления очень неэффективно, а abs () - только для целых чисел.
Мишель Роузич

Неопределенное поведение возможно, когда x == INT_MIN.
chux - Восстановить Монику
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.