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

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

Edit: в частности, я искал функцию, работающую на поплавках.

23 ответов


удивлен никто не оставил дистанционных, типобезопасный язык 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, что кажется расточительным.
  • работает для ints, поплавков, двойников, неподписанных шорт или любых пользовательских типов, создаваемых из целого числа 0 и несортируемые.
  • быстро! copysign медленно, особенно если вам нужно продвигать, а затем снова узким. Это branchless и оптимизирует превосходно
  • стандартам! Взлом bitshift является аккуратным, но работает только для некоторых битовых представлений и не работает, когда у вас есть тип без знака. При необходимости его можно было бы предусмотреть в качестве ручной специализации.
  • точно! Простые сравнения с нул могут поддерживать машину внутреннюю высокоточную представление (например, 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>());
    }
    

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


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

(x > 0) - (x < 0)

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

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

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

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

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


по-видимому, ответ на вопрос оригинального плаката-нет. Нет стандартный C++ .


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

существует ли стандартная функция знака (signum, sgn) в C/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


более быстро чем вышеуказанные решения, включая самое высокое расклассифицированное одно:

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

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

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

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

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


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

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

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

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


тем не менее OP хочет что-то немного отличающийся.

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

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

глубже:

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

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

многие ответы уже покрыл, что, но не адрес x = -0.0, +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;
}

вместо этого предложите эту функциональность: On -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;
}

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


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

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

  • A signum функция должна всегда возвращать тип его операнда, аналогично , потому что 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) - это путь, просто потому, что они также, скорее всего, будут без ветвей и, кроме того, не требуют приведения результата от integer обратно в значение с плавающей запятой. Вероятно, вы должны прокомментировать, что ваши реализации с плавающей запятой signum не будет возвращать ноль из-за особенностей нулевых значений с плавающей запятой, соображений времени обработки, а также потому, что часто очень полезно в арифметике с плавающей запятой получить правильный знак -1/+1, даже для нулевых значений.


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

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


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

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

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


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

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

и я нашел первую функцию - ту, у которой два аргумента, что намного полезнее от "standard" sgn (), потому что она чаще всего используется в коде вроде этого:

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

и

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

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

на самом деле у меня есть этот кусок кода, используя ГГС()

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

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

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

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

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

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

#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 и т. д.)


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


можно использовать boost::math::sign() метод boost/math/special_functions/sign.hpp если boost доступен.


double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }

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

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

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

(x

или, заставляя немного сдвинуть:

(x

но компилятор, скорее всего, оптимизирует это в любом случае.


о:

int sgn = x/fabs(x);

это должно сработать довольно хорошо.


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

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

например:

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

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

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