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

时间:2009-12-14 22:27:15

标签: c++ c math

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

编辑:具体来说,我正在寻找一个处理花车的功能。

24 个答案:

答案 0 :(得分:461)

惊讶没有人发布无分支,类型安全的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很慢,特别是如果你需要提升然后再缩小。这是无分支的,并且极好地优化
  • 符合标准的! bitshift hack很整洁,但只适用于某些位表示,并且当你有一个无符号类型时不起作用。在适当的时候,它可以作为手动专业化提供。
  • 准确!与零进行简单比较可以保持机器的内部高精度表示(例如x87上的80位),并避免过早的舍入到零。

注意事项:

  • 这是一个模板,因此编译需要永远。
  • 显然有些人认为使用一种新的,有些深奥且非常慢的标准库函数甚至没有真正实现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>());
    }
    

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

答案 1 :(得分:258)

我不知道它的标准功能。这是一个有趣的写作方式:

(x > 0) - (x < 0)

这是一种更易读的方法:

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

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

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

答案 2 :(得分:180)

有一个名为copysign()的C99数学库函数,它接受一个参数的符号和另一个参数的绝对值:

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。

答案 3 :(得分:72)

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

答案 4 :(得分:71)

似乎大多数答案都错过了原来的问题。

  

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

不在标准库中,但是copysign可以通过copysign(1.0, arg)几乎以相同的方式使用boosthttp://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/utils/sign_functions.html中有一个真正的符号函数,也可能是标准的一部分。

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

{{3}}

答案 5 :(得分:28)

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

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

答案 6 :(得分:19)

  

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

是的,取决于定义。

C99及更高版本在signbit()

中有<math.h>
  

int signbit(实际浮动x);
  当且仅当其参数值的符号为负时,signbit宏才返回非零值。 C11§7.12.3.6

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

  

我想要一个函数,对于负数返回-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>00上的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;
}

答案 7 :(得分:16)

有一种方法可以在没有分支的情况下完成它,但它不是很漂亮。

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

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

该页面上还有很多其他有趣,过于聪明的东西......

答案 8 :(得分:11)

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

答案 9 :(得分:5)

一般来说,C / C ++中没有标准的signum函数,缺乏这样的基本函数会告诉你很多这些语言。

除此之外,我认为关于定义这种功能的正确方法的大多数观点都是正确的,一旦你考虑到两个重要的警告,关于它的“争议”实际上是一个非争论: / p>

  • 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)的内联函数是可行的方法,因为它们很可能是分支-free,另外不需要将整数的结果转换回浮点值。您可能应该突出地评论 signum 的浮点实现不会返回零,因为浮点零值的特殊性,处理时间考虑因素,以及它在浮点运算中通常非常有用接收正确的-1 / + 1符号,即使是零值。

答案 10 :(得分:4)

我在Nutshell中的C副本揭示了一个名为copysign的标准函数的存在,它可能很有用。看起来copysign(1.0,-2.0)将返回-1.0而copysign(1.0,2.0)将返回+1.0。

非常接近吧?

答案 11 :(得分:3)

不,它在c ++中不存在,就像在matlab中一样。我在我的程序中使用了一个宏。

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

答案 12 :(得分:3)

下面超载的已接受答案确实没有触发 -Wtype-limits

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

答案 13 :(得分:3)

这个问题很旧,但是现在有了这种所需的功能。我添加了一个带有not,左移和dec的包装器。

您可以使用基于signbit from C99的包装函数,以获取确切的所需行为(请参见下面的代码)。

返回x的符号是否为负。
这也可以应用于无限,NaN和零(如果零是无符号的,则视为正数

#include <math.h>

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

注意:我使用操作数不是(“!”),因为signbit的返回值未指定为1(即使示例让我们认为它总是这样),但对于负数为true:

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

然后我将左移(“ << 1”)乘以2,这将使我们得到2表示正数,0表示负数,最后减1分别得到1和-1(正数和负数)根据OP的要求。

答案 14 :(得分:1)

偏离主题,但我用这个:

connect

我发现第一个函数 - 带有两个参数的函数,从“标准”sgn()开始更有用,因为它最常用于这样的代码:

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

VS

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

没有无符号类型的强制转换,也没有额外的减号。

实际上我使用sgn()

获得了这段代码
int comp(unsigned a, unsigned b){
   return sgn(a, b);
}

答案 15 :(得分:1)

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

答案 16 :(得分:0)

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

此功能假定:

  • binary32 表示浮点数
  • 使用名为 union
  • 时,对严格别名规则发出异常的编译器

答案 17 :(得分:0)

虽然接受的答案中的整数解决方案非常优雅,但是我不能为双重类型返回NAN,所以我稍微修改了它。

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

请注意,返回浮点NAN而不是硬编码NAN会导致符号位在some implementations中设置,因此val = -NANval = NAN的输出无论如何都是相同的(如果您希望在nan上输出“-nan”,则可以在返回前放置abs(val) ...)

答案 18 :(得分: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));
}

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

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

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

这适用于IEEE 754 double-precision binary floating-point format: binary64

答案 19 :(得分:-1)

为什么只需使用三元运算符和if-else即可

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

答案 20 :(得分:-2)

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

答案 21 :(得分:-2)

我今天遇到了这个问题。很好,没有标准方式,但......

由于OP只需要放大输出范围并将其重新置于0,( - 1到1而不是0到1),为什么不将它加倍并减去1?

我用过这个:

(X小于0)* 2-1

或者,强制转移:

(X小于0)&LT;&LT; 1-1

但是编译器可能会优化它。

答案 22 :(得分:-3)

怎么样:

int sgn = x/fabs(x);

它应该很好。

答案 23 :(得分:-5)

使用:

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

例如:

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

或者您可能想要使用一些精心设计的代码,但先进行投射:

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