Существует ли стандартная функция знака (signum, sgn) в C/C++?
Я хочу функцию, которая возвращает -1 для отрицательных чисел и +1 для положительных чисел. http://en.wikipedia.org/wiki/Sign_function Достаточно легко написать свою собственную, но это похоже на то, что должно быть где-то в стандартной библиотеке.
Изменить: В частности, я искал функцию, работающую на поплавках.
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, что кажется расточительным.
- Подходит для целых чисел, чисел с плавающей запятой, двойных чисел, шорт без знака или любых пользовательских типов, которые могут быть созданы из целого числа 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>()); }
(Что является хорошим примером первого предупреждения.)
Я не знаю стандартной функции для этого. Вот интересный способ написать это:
(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.
Кажется, что большинство ответов пропустили оригинальный вопрос.
Существует ли стандартная функция знака (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);
Судя по всему, ответ на вопрос автора оригинала - нет. Там нет стандартного C++ sgn
функция.
Существует ли стандартная функция знака (signum, sgn) в C/C++?
Да, в зависимости от определения.
С99 и позже имеет 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
, Многие из них ориентированы на целочисленную точку зрения, в которой обычно отсутствуют номера-не-числа ( 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;
}
Быстрее, чем вышеперечисленные решения, в том числе с самым высоким рейтингом:
(x < 0) ? -1 : (x > 0)
Есть способ сделать это без ветвления, но это не очень красиво.
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
http://graphics.stanford.edu/~seander/bithacks.html
Много других интересных, слишком умных вещей на этой странице тоже...
Если все, что вам нужно, это проверить знак, используйте signbit (возвращает true, если его аргумент имеет отрицательный знак). Не уверен, почему вы хотели бы вернуть -1 или +1; copysign более удобен для этого, но, похоже, он вернет +1 для отрицательного нуля на некоторых платформах с частичной поддержкой отрицательного нуля, где signbit предположительно вернул бы true.
В общем, в 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 знак, даже для нулевых значений.
Моя копия C в двух словах раскрывает существование стандартной функции copysign, которая может быть полезна. Похоже, что copysign(1.0, -2.0) вернет -1.0, а copysign(1.0, 2.0) вернет +1.0.
Довольно близко, а?
Вопрос старый, но теперь есть такая желаемая функция. Я добавил обертку с not, left shift и dec.
Вы можете использовать функцию-оболочку, основанную на signbit из C99, чтобы получить точное желаемое поведение (см. Код ниже).
Возвращает, является ли знак x отрицательным.
Это также может быть применено к бесконечным числам, NaN и нулям (если ноль беззнаковый, он считается положительным
#include <math.h>
int signValue(float a) {
return ((!signbit(a)) << 1) - 1;
}
NB: я использую операнд not ("!"), Потому что возвращаемое значение signbit не указано равным 1 (хотя примеры позволяют нам думать, что так будет всегда), но верно для отрицательного числа:
Возвращаемое значение
Ненулевое значение (истина), если знак x отрицательный; и ноль (ложь) в противном случае.
Затем я умножаю на два со сдвигом влево (" << 1"), что даст нам 2 для положительного числа и 0 для отрицательного числа, и, наконец, уменьшаю на 1, чтобы получить 1 и -1 для соответственно положительных и отрицательных чисел, как требует ОП.
Принятый ответ с приведенной ниже перегрузкой действительно не вызывает -Wtype-limit, однако он вызывает -Wunused-параметр на 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>());
}
Для 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
Нет, его нет в C++, как в matlab. Я использую макрос в моих программах для этого.
#define sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
Ты можешь использовать boost::math::sign()
метод из boost/math/special_functions/sign.hpp
если буст доступен.
Немного не по теме, но я использую это:
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);
}
Вот дружественная ветвлению реализация:
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.
#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)
до возвращения...)
int sign(float n)
{
union { float f; std::uint32_t i; } u { n };
return 1 - ((u.i >> 31) << 1);
}
Эта функция предполагает:
- двоичное представление чисел с плавающей запятой
- компилятор, который делает исключение из правила строгого псевдонима при использовании именованного объединения
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }
Я столкнулся с этим только сегодня. Так хорошо, нет стандартного способа, но...
Поскольку ОП просто необходимо увеличить выходной диапазон и перецентрировать его на 0, (от -1 до 1, а не от 0 до 1), почему бы просто не удвоить его и вычесть 1?
Я использовал это:
(х<0)*2-1
Или, заставляя немного сдвинуть:
(Х<0)<<1-1
Но компилятор, вероятно, все равно оптимизирует это.
Как насчет:
int sgn = x/fabs(x);
это должно получиться довольно хорошо.
Зачем использовать троичные операторы и если-иначе, когда вы можете просто сделать это
#define sgn(x) x==0 ? 0 : x/abs(x)
Использовать:
`#define sgn(x) (x<0)`
например:
`if(sng(n)) { etc ....}`
Или вы можете захотеть использовать какой-то сложный код, но сначала приведите:
inline bool sgn_long(long x)
{
return ((x<0)? true: false);
}