Существует ли стандартная функция знака (signum, sgn) в C/С++?
Мне нужна функция, которая возвращает -1 для отрицательных чисел и +1 для положительных чисел. http://en.wikipedia.org/wiki/Sign_function Достаточно легко написать свои собственные, но это похоже на то, что должно быть где-то в стандартной библиотеке.
Изменить: В частности, я искал функцию, работающую с поплавками.
Удивленный никто еще не опубликовал версию С++ без вежливости:
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, floats, double, unsigned shorts или любых настраиваемых типов, которые можно построить из целого числа 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.
По-видимому, ответ на исходный вопрос с плакатом - нет. Нет стандартной функции С++ sgn
.
Кажется, что большинство ответов пропустили оригинальный вопрос.
Есть ли стандартная функция знака (signum, sgn) в C/C++?
Не в стандартной библиотеке, однако есть copysign
который можно использовать почти таким же образом через copysign(1.0, arg)
и есть функция true sign в 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);
Быстрее, чем вышеупомянутые решения, включая наивысший рейтинг:
(x < 0) ? -1 : (x > 0)
Есть ли стандартная функция знака (signum, sgn) в C/С++?
Да, в зависимости от определения.
C99 и позже имеет макрос signbit()
в <math.h>
int signbit
(real-floatingx
);
Макросsignbit
возвращает ненулевое значение тогда и только тогда, когда знак его аргумента отрицателен. C11 §7.12.3.6
Тем не менее, OP хочет что-то немного другое.
Мне нужна функция, которая возвращает -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
. Многие из них ориентированы на целую точку зрения, которой обычно не хватает Not-a-Numbers (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;
}
Есть способ сделать это без ветвления, но это не очень красиво.
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/С++ нет стандартной функции 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.
Довольно близко, а?
Нет, он не существует в С++, как в matlab. Я использую макрос в своих программах для этого.
#define sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
Принятый ответ с приведенной ниже перегрузкой действительно не вызывает -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>());
}
Для С++ 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.
Бит вне темы, но я использую это:
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);
}
Вы можете использовать метод boost::math::sign()
из boost/math/special_functions/sign.hpp
если boost доступен.
int sign(float n)
{
union { float f; std::uint32_t i; } u { n };
return 1 - ((u.i >> 31) << 1);
}
Эта функция предполагает:
- двоичное представление чисел с плавающей запятой
- компилятор, который делает исключение в правиле строжайшего aliasing при использовании именованного объединения
#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)
перед возвратом...)
Вот дружественная ветвлению реализация:
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.
Зачем использовать троичные операторы и если-иначе, когда вы можете просто сделать это
#define sgn(x) x==0 ? 0 : x/abs(x)
Я столкнулся с этим только сегодня. Так хорошо, нет стандартного способа, но...
Поскольку OP просто необходим для увеличения диапазона вывода и повторного центрирования его на 0, (от -1 до 1 не от 0 до 1), почему бы просто не удвоить его и вычесть 1?
Я использовал это:
(х < 0) * 2-1
Или, заставляя сдвиг бит:
(х < 0) < < 1-1
Но компилятор, скорее всего, оптимизирует это.
использование:
`#define sgn(x) (x<0)`
например:
`if(sng(n)) { etc ....}`
Или вы можете использовать какой-то разработанный код, но сначала кастинг:
inline bool sgn_long(long x)
{
return ((x<0)? true: false);
}
Посмотрите другие вопросы по меткам c++ c math или Задайте вопрос