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

我想要一个函数,返回负数-1和正数+1。 http://en.wikipedia.org/wiki/Sign_function 我自己编写它很容易,但它似乎应该在某个标准库中

编辑:具体来说,我正在寻找一个函数工作在浮动。

489906 次浏览

我不知道它的标准函数。这里有一种有趣的写法:

(x > 0) - (x < 0)

这里有一个更容易理解的方法:

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

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

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

有一种不用分支的方法,但不太好。

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

http://graphics.stanford.edu/~seander/bithacks.html

那一页上还有很多其他有趣的、过于聪明的东西……

我的《C in a Nutshell》揭示了一个叫做copysign的标准函数的存在,它可能很有用。看起来,copyysign(1.0, -2.0)将返回-1.0,而copyysign(1.0, 2.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。

如果你只想测试符号,使用signbit(如果它的参数有负号,则返回true)。 不知道为什么你特别想要返回-1或+1;复印件更方便 但在某些平台上,它会在负0时返回+1 仅部分支持负0,其中signbit可能会返回true

类型安全的c++版本:

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

好处:

  • 实际上实现了signum(-1、0或1)。这里使用copysign的实现只返回-1或1,而不是signum。此外,这里的一些实现返回的是浮点数(或T)而不是int型,这似乎很浪费。
  • 适用于整型、浮点型、双精度型、无符号短型或任何可从整数0构造且可排序的自定义类型。
  • 快!copysign是缓慢的,特别是当你需要提升然后再次缩小。这是无分支的,并且优化得非常好
  • 符合标准的!位移位黑客是整洁的,但只适用于一些位表示,当你有unsigned类型时就不起作用了。在适当的时候,可以提供手工专门化。
  • 准确的!与0的简单比较可以保持机器内部的高精度表示(例如x87上的80位),并避免过早舍入到0。

警告:

  • 这是一个模板,所以在某些情况下可能需要更长的时间来编译。

  • 显然,有些人认为使用一个新的、有点深奥的、非常慢的标准库函数它甚至没有真正实现signum更容易理解。

  • 检查的< 0部分在为unsigned类型实例化时触发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) ? -1 : (x > 0)
int sign(float n)
{
union { float f; std::uint32_t i; } u { n };
return 1 - ((u.i >> 31) << 1);
}

这个函数假设:

  • 浮点数的binary32表示
  • 在使用命名联合时创建关于严格别名的异常规则的编译器

显然,最初的帖子的问题的答案是否定的。没有标准 c++ sgn函数。

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

似乎大多数答案都忽略了最初的问题。

在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

不,它在c++中不存在,就像在matlab中一样。我在程序中使用宏来实现这一点。

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

一般来说,在C/ c++中没有标准的signum函数,缺少这样一个基本函数说明了很多关于这些语言的信息。

除此之外,我相信关于定义这样一个函数的正确方法的两种主流观点在某种程度上是正确的,而且一旦你考虑到两个重要的警告,关于它的“争议”实际上是没有争议的:

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

  • 浮点类型没有一个精确的零值:+0.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)的内联函数是可行的,因为它们也很可能是无分支的,而且不需要将整数的结果转换回浮点值。你可能应该明确指出,你的的浮点实现不会返回零,因为浮点零值的特性,处理时间的考虑,也因为在浮点算术中,接收正确的-1/+1符号通常非常有用,即使是零值。

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

是的,这取决于定义。

C99及以后版本在<math.h>中有signbit()

int signbit(real-floating x) signbit宏当且仅当其参数值的符号为负时返回一个非零值。C11§7.12.3.6 < / p >


然而OP想要一些不同的东西。

我想要一个函数,返回-1的负数和+1的正数. ...处理浮点数的函数。

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

更深层次的问题:

在以下情况下,OP的问题不是特定的:x = 0.0, -0.0, +NaN, -NaN

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

许多答案已经涵盖了这一点,但没有解决x = -0.0, +NaN, -NaN。许多都是为通常缺少Not-a-Numbers ()和-0.0的整数观点而设计的。

-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;
}

下面重载的接受答案确实不会触发-Wtype-limits。但它确实会触发未使用的参数警告(在is_signed变量上)。为了避免这些,第二个参数不应该这样命名:

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上的任何警告。

有点跑题了,但我用了这个:

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);
}

虽然接受的答案中的整数解决方案相当优雅,但它不能为双类型返回NAN,因此我对它进行了稍微修改。

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)…)

为什么使用三元操作符和if-else,当你可以简单地这样做

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

如果boost可用,则可以使用boost/math/special_functions/sign.hpp中的boost::math::sign()方法。

这是一个分支友好的实现:

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架构上,完全无分支的版本可能更快:

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

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

这个问题很老了,但现在有了这种理想函数。我用not, left shift和dec添加了一个包装器。

您可以使用基于来自C99的符号位的包装器函数来获得确切的期望行为(参见下面的代码)。

返回x的符号是否为负 这也可以应用于无穷大,nan和零(如果零是无符号的,它被认为是正的

#include <math.h>


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

注意:我使用operand not("!")是因为符号位的返回值没有指定为1(即使例子让我们认为它总是这样),但对于负数是真的:

< p > < br >返回值 如果x的符号为负,则为非零值(true);否则为0 (false)。

然后我用左移乘以2(“<<1"),它将为正数提供2,为负数提供0,最后减去1,分别为OP所要求的正数和负数得到1和-1。