it-swarm.com.ru

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

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

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

370
batty

Удивило, что еще никто не опубликовал версию 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 вызывает 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>());
    }
    

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

464
user79758

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

(x > 0) - (x < 0)

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

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

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

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
258
Mark Byers

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

181
comingstorm

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

73
John

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

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

72
Catskul

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

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

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

Deeper:

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

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

Многие ответы уже охватили это, но не обращаются к x = -0.0, +NaN, -NaN. Многие из них ориентированы на целочисленную точку зрения, в которой обычно отсутствуют номера-не-числа ( NaN ) и - 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;
}
21
chux

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

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

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

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

16
Tim Sylvester

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

11
ysth

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

5
Tabernakel

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

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

4
High Performance Mark

Принятый ответ с приведенной ниже перегрузкой действительно не вызывает - 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 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.

3
SamVanDonut

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

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

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

1
khkarens

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

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

vs.

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
Nick

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

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 .

0
Serge Rogatch

Хотя целочисленное решение в принятом ответе довольно элегантно, меня беспокоило, что оно не сможет вернуть 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
mrclng
int sign(float n)
{     
  union { float f; std::uint32_t i; } u { n };
  return 1 - ((u.i >> 31) << 1);
}

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

  • binary32 представление чисел с плавающей точкой
  • компилятор, который создает правило исключение из-за строгого алиасинга при использовании named union
0
Gigi