C / C ++中是否有标准符号函数(signum,sgn)?

Is there a standard sign function (signum, sgn) in C/C++?

我想要一个函数,对于负数返回-1,对于正数返回+1。http://en.wikipedia.org/wiki/sign_函数我自己写这本书很容易,但它似乎应该放在某个标准库的某个地方。

编辑:特别是,我在寻找一个处理浮动的函数。


惊讶的是没有人发布了无分支、类型安全的C++版本:

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

效益:

  • 实际上实现signum(-1、0或1)。这里使用copysign的实现只返回-1或1,这不是signum。此外,这里的一些实现返回的是浮点(或T),而不是int,这看起来很浪费。
  • 适用于int、float、double、unsigned short或任何可从integer 0和orderable构造的自定义类型。
  • 快!copysign的速度很慢,特别是当你需要提升然后再缩小的时候。这是无分支的,优化非常出色
  • 符合标准!bitshift hack很简洁,但只适用于某些位表示,并且在有无符号类型时不起作用。在适当的时候,它可以作为手动专业化提供。
  • 准确!与零的简单比较可以保持机器的内部高精度表示(例如x87上的80位),并避免过早的归零。

Caveats:

  • 它是一个模板,因此需要永远编译。
  • 显然,有些人认为使用一个新的、有点深奥的、非常缓慢的标准库函数,甚至不真正实现signum,这是可以理解的。
  • 当为无符号类型实例化时,检查的< 0部分触发GCC的-Wtype-limits警告。您可以通过使用一些重载来避免这种情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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>());
    }

    (这是第一个警告的好例子。)


我不知道它的标准函数。以下是一种有趣的写作方式:

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

以下是一种更易读的方法:

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

如果您喜欢三元运算符,可以这样做:

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


有一个名为copysign()的c99数学库函数,它从一个参数中获取符号,从另一个参数中获取绝对值:

1
2
3
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。


显然,对原始海报的问题的答案是否定的。没有标准的C++ EDCOX1×3函数。


似乎大多数答案都漏掉了原来的问题。

Is there a standard sign function (signum, sgn) in C/C++?

在标准库中没有,但是有可以通过copysign(1.0, arg)以几乎相同的方式使用的copysign,在boost中有一个真正的符号函数,它可能也是标准的一部分。

1
2
3
4
5
    #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


比上述解决方案更快,包括最高评级的解决方案:

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


Is there a standard sign function (signum, sgn) in C/C++?

是的,取决于定义。

C99及更高版本的signbit()宏位于中。

int signbit(real-floating x);
The signbit macro returns a nonzero value if and only if the sign of its argument value is negative. C11 §7.12.3.6

但是OP想要一些不同的东西。

I want a function that returns -1 for negative numbers and +1 for positive numbers. ... a function working on floats.

1
#define signbit_p1_or_n1(x)  ((signbit(x) ?  -1 : 1)

更深的:

在以下情况下,该职位并不具体,x = 0.0, -0.0, +NaN, -NaN

经典的signum()返回+1x>0上,-1x>0上,0x==0上。

许多答案已经涵盖了这一点,但没有涉及到x = -0.0, +NaN, -NaN。许多都是针对一个整数的观点,通常缺少非数字(NaN)和-0.0。

典型答案在-0.0, +NaN, -NaN上的作用类似于signnum_typical(),它们返回0.0, 0.0, 0.0

1
2
3
4
5
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

1
2
3
4
5
double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}


有一种不需要分支就可以完成的方法,但它不是很漂亮。

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

http://graphics.斯坦福.edu/~seander/bithacks.html

那一页上还有很多其他有趣的,非常聪明的东西…


如果您只想测试符号,请使用signbit(如果其参数有负号,则返回true)。不知道为什么您特别希望返回-1或+1;copysign更方便但在某些平台上,它会返回+1,表示负零。只有部分支持负零,其中signbit可能返回true。


一般来说,C/C++中没有标准的符号函数,而缺少这样一个基本的功能,这说明了很多语言。

除此之外,我相信关于定义这样一个函数的正确方法的大多数观点都是正确的,而且一旦考虑到两个重要的警告,"争论"实际上是没有争议的:

  • signum函数应始终返回其操作数的类型,类似于abs()函数,因为signum通常用于在以某种方式处理后与绝对值相乘。因此,符号的主要用例不是比较而是算术,后者不应该涉及任何昂贵的整数到/从浮点转换。

  • 浮点类型没有一个精确的零值:+0.0可以解释为"零上无穷大",-0.0可以解释为"零下无穷大"。这就是为什么涉及零的比较必须在内部检查这两个值的原因,并且像EDOCX1[1]这样的表达式可能很危险。

关于C,我认为使用整型的最好方法确实是使用(x > 0) - (x < 0)表达式,因为它应该以无分支的方式转换,并且只需要三个基本操作。最好定义执行与参数类型匹配的返回类型的内联函数,并添加一个c11 define _Generic以将这些函数映射到公共名称。

对于浮点值,我认为基于c11 copysignf(1.0f, x)copysign(1.0, x)copysignl(1.0l, x)的内联函数是可行的,因为它们也很可能是无分支的,而且不需要将整数的结果转换回浮点值。您可能需要特别指出的是,由于浮点零值的特殊性、处理时间的考虑,以及在浮点算术中接收正确的-1/+1符号(即使是零值)通常非常有用,因此符号的浮点实现不会返回零。


简而言之,我复制的C揭示了一个称为copysign的标准函数的存在,这可能是有用的。看起来copysign(1.0,-2.0)将返回-1.0,copysign(1.0,2.0)将返回+1.0。

很接近啊?


下面的重载接受的答案确实不会触发-wtype限制,但是它会在is_signed参数上触发-wunused参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,另一种选择可能是。

1
2
3
4
5
6
7
8
9
10
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中一样。为此,我在程序中使用了宏。

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


有点偏离主题,但我用这个:

1
2
3
4
5
6
7
8
9
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));
}

我发现第一个函数——有两个参数的函数,在"standard"sgn()中更有用,因为它最常用于这样的代码:

1
2
3
int comp(unsigned a, unsigned b){
   return sgn( int(a) - int(b) );
}

VS

1
2
3
int comp(unsigned a, unsigned b){
   return sgn(a, b);
}

无符号类型没有强制转换,也没有其他减号。

实际上,我有一段代码使用sgn()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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/special_functions/sign.hpp中的boost::math::sign()方法。


下面是一个支持分支的实现:

1
2
3
4
inline int signum(const double x) {
    if(x == 0) return 0;
    return (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

除非数据的0是数字的一半,否则分支预测器将选择其中一个分支作为最常见的分支。两个分支只涉及简单的操作。

或者,在某些编译器和CPU体系结构上,完全无分支的版本可能更快:

1
2
3
4
inline int signum(const double x) {
    return (x != 0) *
        (1 - (static_cast<int>((*reinterpret_cast<const uint64_t*>(&x)) >> 63) << 1));
}

这适用于IEEE754双精度二进制浮点格式:binary64。


1
2
3
4
5
int sign(float n)
{    
  union { float f; std::uint32_t i; } u { n };
  return 1 - ((u.i >> 31) << 1);
}

此函数假定:

  • 浮点数的二进制32表示法
  • 使用命名联合时,对严格别名规则进行异常处理的编译器。


虽然接受的答案中的整数解决方案相当优雅,但它无法为双类型返回NaN,这让我感到困扰,因此我对它进行了轻微的修改。

1
2
3
template <typename T> double sgn(T val) {
    return double((T(0) < val) - (val < T(0)))/(val == val);
}

注意,与硬编码的NAN相反,返回浮点NaN会导致在某些实现中设置符号位,因此,val = -NANval = NAN的输出将是相同的,无论怎样(如果您喜欢"NAN输出而不是-nan,您可以在返回之前放置abs(val)…)


为什么要使用三元运算符,如果不是,当您可以简单地执行此操作时

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


我就在今天碰到这个。好吧,没有标准的方法,但是…

既然OP只需要放大输出范围并将其重新集中在0上,(-1到1而不是0到1),为什么不把它加倍并减去1呢?

我用过这个:

(x<0)*2-1

或者,强制进行位移位:

(x<0)<1-1

但无论如何,编译器可能会对此进行优化。


1
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }

如何:

1
int sgn = x/fabs(x);

结果应该不错。


用途:

1
`#define sgn(x) (x<0)`

例如:

1
`if(sng(n)) { etc ....}`

或者您可能希望使用一些详细的代码,但首先要强制转换:

inline bool sgn_long(long x)
{
return ((x<0)? true: false); }