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

305

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

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

  • 9
    Что он должен вернуть за 0?
  • 52
    @Craig McQueen; это зависит от того, положительный ноль или отрицательный ноль.
Показать ещё 8 комментариев
Теги:
math

21 ответ

385
Лучший ответ

Удивленный никто еще не опубликовал версию С++ без вежливости:

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, что кажется расточительным.
  • Работает для ints, floats, double, unsigned shorts или любых настраиваемых типов, которые можно построить из целого числа 0 и упорядочиваемого.
  • Быстро! copysign медленный, особенно если вам нужно продвигать, а затем снова сузить. Это бесветровое и прекрасно оптимизируется
  • соответствующий стандартам! Хит взлома является аккуратным, но работает только для некоторых представлений бит и не работает, когда у вас есть неподписанный тип. В случае необходимости он может быть предоставлен в качестве ручной специализации.
  • Точная! Простые сравнения с нулем могут поддерживать внутреннее высокоточное представление машины (например, 80 бит на x87) и избегать преждевременного раунда до нуля.

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

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

    (Это хороший пример первой оговорки.)

  • 4
    «Это шаблон, так что на его компиляцию уйдет целая вечность». А? Я имею в виду, что технически это не совсем "скомпилировано", просто прошло один раз. Когда вы создаете его экземпляр, он компилируется, но не медленнее, чем один, написанный вручную с определенным T
  • 16
    @GMan: GCC только сейчас (4.5) перестал иметь квадратичную стоимость по отношению к количеству экземпляров для шаблонных функций, и они все еще значительно дороже для анализа и создания экземпляров, чем написанные вручную функции или стандартный препроцессор Си. Компоновщик также должен проделать дополнительную работу по удалению дублирующихся экземпляров. Шаблоны также поощряют # include-in-# includes, что делает вычисление зависимостей более длительным и небольшими (часто реализация, а не интерфейс) изменениями, чтобы принудительно перекомпилировать больше файлов.
Показать ещё 21 комментарий
228

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

(x > 0) - (x < 0)

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

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

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

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
  • 6
    Также тот, который работает без веток. Ницца.
  • 2
    Было бы интересно рассчитать время против x <0? -1: 1 и 1-2 * (x <0).
Показать ещё 21 комментарий
157

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

  • 52
    Проголосовал этот, самый популярный ответ. Оставленный в изумлении, что SO-сообщество, похоже, предпочитает использовать стандартную библиотечную функцию. Пусть боги программирования обрекают всех вас на попытки расшифровки хаков, используемых умными программистами, незнакомыми с языковыми стандартами. Да, я знаю, что это будет стоить мне тонны повторения на SO, но я бы предпочел быструю бурю, чем остальные из вас ...
  • 27
    Это близко, но это дает неправильный ответ для нуля (по крайней мере, согласно статье Википедии в вопросе). Хорошее предложение, хотя. +1 в любом случае.
Показать ещё 7 комментариев
64

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

  • 2
    На самом деле есть один: en.cppreference.com/w/cpp/numeric/math/copysign . Используйте: std :: copysign (1, x); Обратите внимание, что тип возвращаемого значения не является целочисленным.
56

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

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

Не в стандартной библиотеке, но есть 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
    Это должен быть ответ с наибольшим количеством голосов, так как он дает максимально возможное решение того, что задано в вопросе.
  • 0
    Последние несколько минут я удивлялся, почему в стандартной библиотеке нет функции подписи. Это так часто встречается - определенно чаще используется, чем гамма-функция, которую можно найти в заголовке cmath.
Показать ещё 1 комментарий
28

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

(x < 0) ? -1 : (x > 0)
  • 0
    Какой тип х? Или вы используете #define?
  • 3
    Ваш тип не быстрее. Это часто приводит к отсутствию кэша.
Показать ещё 3 комментария
14

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

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); ,
  • 1
    Не совсем то, что мы называем переносимым кодом ...
10

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

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

C99 и позже имеет макрос signbit() в <math.h>

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


Тем не менее, OP хочет что-то немного другое.

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

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

Deeper:

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

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

Многие ответы уже рассмотрели это, но не адресуйте x = -0.0, +NaN, -NaN. Многие из них ориентированы на целую точку зрения, которой обычно не хватает Not-a-Numbers (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;
}
  • 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/…
10

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

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

В общем случае в 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, даже для нулевых значений.

4

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

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

  • 0
    Не стандартно, но может быть широко доступно. Microsoft начинает с подчеркивания, которое они используют для нестандартных расширений. Не лучший выбор, когда вы работаете с целыми числами.
  • 5
    copysign соответствует стандартам ISO C (C99) и POSIX. См. Opengroup.org/onlinepubs/000095399/functions/copysign.html
Показать ещё 1 комментарий
3

Принятый ответ с перегрузкой ниже действительно не вызывает -Wtype-limits, однако он запускает -Wunused-parameter в аргументе is_signed.

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

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

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

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

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

  • 0
    Чтобы избежать предупреждения -Wunused-parameter просто используйте неназванные параметры.
  • 0
    Это на самом деле очень верно. Я пропустил это. Тем не менее, мне нравится альтернатива C ++ 11 в любом случае.
3

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

#define sign(a) ( ( (a) < 0 )  ?  -1   : ( (a) > 0 ) )
  • 4
    Надо отдавать предпочтение шаблонам над макросами в C ++.
  • 0
    В C нет шаблона ...... helloacm.com/how-to-implement-the-sgn-function-in-c
Показать ещё 2 комментария
1

Бит вне темы, но я использую это:

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);
}
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
#include<iostream>
#include<math.h>
using namespace std;
int main()
{
float k=10;
cout<<bool signbit(k); /* bool signbit(arg) will return "0" if arg passed is + 
                         else "1" */
return 0;
}

Вышеприведенный код может не служить вашей цели (получение 1 или -1), но это, конечно, упрощает оформление знака типа данных (int, float, double и т.д.)

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

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

  • двоичное представление чисел с плавающей запятой
  • компилятор, который делает исключение в правиле строжайшего aliasing при использовании именованного объединения
  • 3
    Здесь все еще есть некоторые плохие предположения. Например, я не верю, что порядковый номер типа float гарантированно является порядковым номером целого числа. Ваша проверка также не проходит на любой архитектуре, использующей ILP64. На самом деле, вы просто переопределяете copysign ; если вы используете static_assert у вас есть C ++ 11, и вы можете использовать copysign .
-1
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }
-2

Я столкнулся с этим только сегодня. Так хорошо, нет стандартного способа, но...

Поскольку OP просто необходим для увеличения диапазона вывода и повторного центрирования его на 0, (от -1 до 1 не от 0 до 1), почему бы просто не удвоить его и вычесть 1?

Я использовал это:

(х < 0) * 2-1

Или, заставляя сдвиг бит:

(х < 0) < < 1-1

Но компилятор, скорее всего, оптимизирует это.

-3

Как насчет:

int sgn = x/fabs(x);

он должен хорошо работать.

  • 5
    Что происходит, когда х == 0,0?
  • 2
    и производительность тоже будет довольно плохой
-5

использование:

`#define sgn(x) (x<0)` 

например:

`if(sng(n)) { etc ....}`

Или вы можете использовать какой-то разработанный код, но сначала кастинг:

inline bool sgn_long(long x) { return ((x<0)? true: false); }

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню