Чи є в C / C ++ стандартна функція знаку (signum, sgn)?


409

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

Редагувати: Зокрема, я шукав функцію, що працює над поплавками.


13
Що має повернути 0?
— Крейг МакКуїн

61
@Craig McQueen; це залежить від того, чи є це додатний нуль або негативний нуль.
— ysth

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

6
@ysth @Craig McQueen, помилковий для плавців теж, ні? Визначення sgn (x) говорить повернути 0, якщо x==0. Відповідно до IEEE 754 , від’ємний нуль і додатний нуль повинні порівнюватися як рівні.
— RJFalconer

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

Відповіді:


506

Здивований, що ще ніхто не опублікував безпечну для версії C ++ версію:

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

Переваги:

  • Фактично реалізує signum (-1, 0 або 1). Тут реалізація, яка використовує copysign, повертає лише -1 або 1, що не є синглом. Також деякі реалізації тут повертають float (або T), а не int, що здається марнотратним.
  • Працює для ints, floats, double, непідписаних шортів або будь-яких спеціальних типів, які можна сконструювати з цілого числа 0 та замовити.
  • Швидко! copysignповільно, особливо якщо вам потрібно просувати, а потім знову звужуватися. Це без гілок і оптимізується на відмінно
  • Відповідність стандартам! Зламаний біт є чітким, але працює лише для декількох представлень бітів і не працює, якщо у вас непідписаний тип. Це може бути надано як ручна спеціалізація, коли це доречно.
  • Точні! Прості порівняння з нулем можуть підтримувати внутрішнє високоточне представлення машини (наприклад, 80 біт на x87), а також уникнути передчасного раунду до нуля.

Застереження:

  • Це шаблон, так що за певних обставин може знадобитися більше часу.
  • Мабуть, деякі люди вважають, що використання нової, дещо езотеричної та дуже повільної стандартної бібліотечної функції , яка навіть не реалізує вхід, є більш зрозумілим.
  • < 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) перестав витрачати квадратичну кількість екземплярів для функцій шаблону, і вони все ще значно дорожчі для розбору та інстанції, ніж функції, записані вручну, або стандартний C-попередник. Лінкер також повинен зробити більше роботи, щоб видалити дублікати екземплярів. Шаблони також заохочують # включає в себе # включає, що змушує обчислення залежності тривати довше і невеликі (часто впровадження, а не інтерфейс) змін, щоб змусити перекомпілювати більше файлів.

15
@Joe: Так, і досі немає помітних витрат. C ++ використовує шаблони, це просто те, що ми всі повинні зрозуміти, прийняти та подолати.
— GManNickG

42
Зачекайте, що це за "copysign is slow" бізнес ...? Використання поточних компіляторів (g ++ 4.6+, clang ++ 3.0), std::copysignсхоже, для мене відмінний код: 4 інструкції (вбудовані), відсутність розгалуження, повністю використовуючи FPU. Рецепт, наведений у цій відповіді, навпаки, генерує набагато гірший код (ще багато вказівок, включаючи множення, переміщення вперед-назад між цілою одиницею та FPU) ...
— snogglethorpe

14
@snogglethorpe: Якщо ви закликаєте copysignдо int, він сприяє плаванню / подвійності, а після повернення повинен знову звузитися. Ваш компілятор може оптимізувати цю рекламну акцію, але я не можу знайти нічого, що б підказало, що це гарантується стандартом. Крім того, щоб здійснити реєстрацію через 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"false"; будь-яке інше значення - "справжнє"; однак оператори реляції та рівності завжди повертаються 0або 1(див. Стандарт 6.5.8 та 6.5.9). - значення виразу a * (x == 42)дорівнює 0або a.
— pmg

21
Знак високої продуктивності, я вражений тим, що ви пропустили тег C ++. Ця відповідь дуже справедлива і не заслуговує на голосування. Більше того, я б не використовував copysignінтеграл, xнавіть якби мав його в наявності.
— авакар

6
Хтось насправді перевіряв, який код GCC / G ++ / будь-який інший компілятор випускає на реальну платформу? Я здогадуюсь, що версія "без гілок" використовує дві гілки замість однієї. Бітшифтінг, напевно, набагато швидший - і більш портативний з точки зору продуктивності.
— Jørgen Fogh

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 надає перевагу хаку перед використанням стандартної функції бібліотеки. Нехай боги програмування засудять вас усіх у спробі розшифрувати хаки, які застосовують розумні програмісти, незнайомі з мовними стандартами. Так, я знаю, що це обійдеться мені в тону повторень на ЗО, але я скоріше ставлюсь до бурхливої ​​бурі, ніж у всіх вас ...
— Висока ефективність Відмітка

34
Це близько, але це дає неправильну відповідь за нуль (відповідно до статті Вікіпедії у питанні хоча б). Хоча приємна пропозиція. +1 у будь-якому випадку
— Марк Байєрс

4
Якщо ви хочете ціле число, або якщо ви хочете отримати точний результат підписання нулів, мені подобається відповідь Марка Байєрса, яка є по-справжньому елегантною! Якщо ви не переймаєтесь вищезазначеним, copysign () може мати підвищення продуктивності, залежно від програми - якби я оптимізував критичний цикл, я спробував би обидва.
— наступає буря

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

5
Спаситель! Потрібен спосіб визначити між -0,0 та 0,0
— flafur Waage

79

Здається, більшість відповідей пропустили оригінальне запитання.

Чи є в C / C ++ стандартна функція знаку (signum, sgn)?

Немає в стандартній бібліотеці, однак є така, 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
Пояснення, яке я часто отримую для подібних питань, - це "досить просто реалізувати себе", що не має вагомих причин. Це повністю переживає проблеми, де стандартизація, непомітні крайові корпуси та де можна застосувати такий широко використовуваний інструмент.
— Кацкуль

77

Мабуть, відповідь на питання оригінального афіші - ні. Немає стандартноїsgn функції C ++ .


2
@SR_ Ви неправі. copysign()не зробить ваш перший параметр 0,0, якщо другий - 0,0. Іншими словами, Іван правильний.
— Алексіс Вільке

29

Швидше, ніж вищезазначені рішення, включаючи найвищий рейтинг:

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

1
Який тип х? Або ви використовуєте #define?
— Шанс

3
Ваш тип не швидший. Це стане причиною пропуску кешу досить часто.
— Джефрі Дрейк

19
Кеш пропустити? Я не знаю як. Можливо, ви мали на увазі галузеве непередбачення?
— Catskul

2
Мені здається, це призведе до попередження про заплутаність цілих і булевих типів!
— сергіол

як це буде швидко з гілкою?
— Нік

29

Чи є в C / C ++ стандартна функція знаку (signum, sgn)?

Так, залежно від визначення.

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. Багато хто орієнтований на цілу точку зору, якій зазвичай не вистачає Not-a-Numbers ( 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

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 boson

1
це означає, що vце цілий тип не ширший, ніж int
— phuclv

12

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


7
Існує багато математичних додатків, у яких знак (x) необхідний. Інакше я просто зробив би if (x < 0).
— Шанс

5

Загалом, у C / C ++ немає стандартної функції signum, і відсутність такої фундаментальної функції багато що говорить про ці мови.

Крім того, я вважаю, що обидві точки зору більшості щодо правильного підходу до визначення такої функції є певним чином правильним, і "суперечка" щодо неї насправді не є аргументом після врахування двох важливих застережень:

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

  • Типи з плаваючою комою не містять єдиного точного нульового значення: +0.0 можна інтерпретувати як "нескінченнозначно вище нуля", а -0,0 - як "нескінченно мало нижче нуля". Ось чому порівняння, що включає нуль, має внутрішньо перевіряти обидві значення, а вираз подібний x == 0.0може бути небезпечним.

Щодо С, я думаю, що найкращий шлях вперед із інтегральними типами - це справді використання (x > 0) - (x < 0)виразу, як це слід перекладати без віток, і вимагає лише трьох основних операцій. Найкраще визначити вбудовані функції, які застосовують тип повернення, що відповідає типу аргументу, і додайте C11define _Generic для зіставлення цих функцій із загальним іменем.

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


5

Моя копія С в горішці розкриває існування стандартної функції під назвою 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 ++.
— Руслан

У 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попередження, просто використовуйте параметри без назви.
— Jonathan Wakely

Це насправді дуже вірно. Я пропустив це. Однак мені більше подобається альтернатива 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) );
}

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

Питання старе, але зараз існує така бажана функція. Я додав обгортку з не, зсув вліво і розм.

Ви можете використовувати функцію обгортки, засновану на вивісці від C99 , щоб отримати точну бажану поведінку (див. Код далі нижче).

Повертає, чи знак x негативний.
Це також можна застосувати до нескінченних, NaN та нулів (якщо нуль не підписано, це вважається позитивним

#include <math.h>

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

Примітка: я використовую операнд не ("!"), Оскільки значення, що повертається, не визначається як значення 1 (навіть якщо приклади дозволяють нам вважати, що це завжди було б так), але вірно для від'ємного числа:

Повернене значення
Ненульове значення (вірно), якщо знак x негативний; а нуль (помилково) в іншому випадку.

Тоді я помножую на два з лівим зсувом ("<< 1"), який дасть нам 2 для додатного числа і 0 для від'ємного і, нарешті, зменшення на 1, щоб отримати 1 і -1 для відповідно додатних і від'ємних чисел, як цього вимагає ОП.


0 буде позитивним і тоді ... що може бути, а може і не бути тим, чого хотів ОП ...
— Анті Хаапала

добре, ми ніколи не можемо знати, що дійсно хотіла ОП, якщо 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" висновку над a, який -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
Тут є ще погані припущення. Наприклад, я не вірю, що цінність поплавця гарантується як цілісність цілого числа. Ваш чек також не відповідає будь-якій архітектурі, що використовує ILP64. Дійсно, ти просто повторюєшся copysign; якщо ви використовуєте, у static_assertвас є C ++ 11, і ви можете також реально використовувати copysign.


-3

Навіщо використовувати потрійні оператори та інше, коли ви можете це просто зробити

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

3
У вашому визначенні також використовується потрійний оператор.
— Мартін Р

Так, безумовно, але він просто використовує один потрійний оператор для розділення нульових і ненульових чисел. Інші версії включають вкладені потрійні ops для розділення позитивного, від’ємного та нульового.
— Jagreet

Використання цілого поділу дуже неефективно, а abs () - лише для цілих чисел.
— Мішель Рузич

Невизначена поведінка можлива, коли x == INT_MIN.
— chux
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.