Чи є в 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.