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

bat*_*tty 387 c c++ math

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

编辑:具体来说,我正在寻找一个工作浮动的功能.

小智 486

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

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 实际上实现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>());
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 第一个版本不是无分支的.为什么人们认为表达式中使用的比较不会生成分支?它将适用于大多数架构.只有具有cmove(或预测)的处理器才会生成无分支代码,但是它们也会为三元组执行,或者如果是胜利则运行/ else. (52认同)
  • 等等,这个"copysign很慢"的业务是什么......?使用当前的编译器(g ++ 4.6 +,clang ++ 3.0),`std :: copysign`似乎为我产生了_excellent_代码:4个指令(内联),没有分支,完全使用FPU.相比之下,这个答案中给出的配方会产生更糟糕的代码(更多的指令,包括乘法,在整数单位和FPU之间来回移动)...... (41认同)
  • @GMan:刚才GCC(4.5)停止了模板函数实例化的成本二次方,并且它们比手动编写的函数或标准C预处理器解析和实例化的成本更高.链接器还需要做更多工作来删除重复的实例化.模板还鼓励#include-in- #include,这使得依赖项计算需要更长时间和更小(通常是实现,而不是接口)更改以强制重新编译更多文件. (17认同)
  • @Joe:是的,但仍然没有明显的成本.C++使用模板,这只是我们必须理解,接受和克服的东西. (15认同)
  • @snogglethorpe:如果你在int上调用`copysign`它会提升为float/double,并且必须在返回时再缩小.您的编译器可能会优化该促销,但我找不到任何暗示标准保证的内容.另外要通过copysign实现signum,你需要手动处理0情况 - 请确保在任何性能比较中包含它. (14认同)
  • 好吧,我的评论是旧的(8 年),但这里有一些精确性,因为我的散文还远不够清晰:我的评论不仅限于 x86,而且我混淆了 `cmov` 和 `setg` (或其他 cpu 的等效内容)。这意味着上面的代码不能保证在所有架构上都是无分支的。您可以使用不同的 godbolt 链接进行检查,代码在 AVR、MPS430 上以及使用 Visual-C 时(甚至在 x86 上)不是无分支的。如果没有这些精确性,我的评论确实是错误的和误导性的。 (6认同)
  • “此外,这里的一些实现返回浮点(或 T)而不是整数,这似乎很浪费。”注意,在相当典型的设置中,符号函数的结果立即在浮点中使用,这并不是那么浪费。点表达式,因此 int 无论如何都会转换为浮点值。 (5认同)
  • "这是一个模板,因此编译需要永远." 咦?我的意思是从技术上来说它根本就不是"编译"的,只是通过了一次.当你实例化它时,它会被编译,但不会慢于一个用特定的`T`手动编写的. (4认同)
  • @VioletGiraffe是的,这是有保证的. (4认同)
  • 如果您认为C++的编译模型"没有明显的成本",那么您从未有过在大型C项目上工作的乐趣,链接器需要确定的时间和周转时间,而不是数小时.(另外,你似乎在争论我的答案没有真正的警告 - 也许你应该投票?) (3认同)
  • @tristopia:你能给我一个例子(在具有合理设置的通用编译器/架构上)生成分支的地方(而不是因为它识别成语并使用更高效的分支版本)? (3认同)
  • 通过将主表达式重写为`return(T(0)<val) - (val <T(0));`它只需要`operator <`定义.你也可以通过明确地将`T(0)`分配给临时来避免两个临时(和构造函数).编译器_不能对更复杂的类型`T`进行优化. (3认同)
  • @VioletGiraffe`bool`可以隐式转换为`int`.`false`转换为零,`true`转换为1. (3认同)
  • @PatrickSchlüter:我很困惑为什么你认为第一个版本需要`cmov`或预测以避免分支.在x86上,这可以通过`setg`轻松实现无分支,如下所示:https://godbolt.org/g/XNst9Z我对PowerPC或ARM不太了解,但是从godbolt.org上玩,它会出现那里也没有分支. (3认同)
  • @Joe:我同意你的观点,但我认为你夸大了成本; 单个功能模板肯定没有明显的成本. (2认同)
  • @GMan:单个符号位没有时间复制,但我敢打赌,如果你正在读这个,你想要复制多个符号位,如果你正在编写C++,你有多个模板函数. .. (2认同)
  • 武汉理工大学?如何减去`bool符合标准? (2认同)
  • 这个答案完全没有抓住要点:它没有处理负零的有趣情况。 (2认同)
  • 我很困惑为什么 @PatrickSchlüter 的评论(据我所知)完全不正确却有如此多的积极投票。表达式中使用的比较*不会*在 x86 上生成分支或“cmov”。 (2认同)

Mar*_*ers 266

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

(x > 0) - (x < 0)
Run Code Online (Sandbox Code Playgroud)

这是一种更易读的方法:

if (x > 0) return 1;
if (x < 0) return -1;
return 0;
Run Code Online (Sandbox Code Playgroud)

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

(x > 0) ? 1 : ((x < 0) ? -1 : 0)
Run Code Online (Sandbox Code Playgroud)

  • 高性能标记,我很惊讶你错过了C++标签.这个答案非常有效,不值得投票.而且,即使我有可用,我也不会使用`copysign`作为整数`x`. (21认同)
  • @Svante:不完全是.值"0"是"假"; 任何其他价值都是"真实的"; 但是,关系运算符和相等运算符总是返回"0"或"1"(参见标准6.5.8和6.5.9). - 表达式"a*(x == 42)"的值是"0"或"a". (11认同)
  • 还有一个没有分支的工作.尼斯. (8认同)
  • Mark Ransom,你的表达式为`x == 0`提供了错误的结果. (6认同)
  • 有没有人真正检查过GCC/G ++ /任何其他编译器在真实平台上发出的代码?我的猜测是"无分支"版本使用两个分支而不是一个分支.Bitshifting可能要快得多 - 而且在性能方面更便携. (6认同)
  • @Svante:"如果指定的关系为真,则每个运算符`<`,`>`...将产生1,如果为假,则为0" (3认同)
  • 对x <0?-1:1和1-2*(x <0)的时间进行计时会很有趣. (2认同)
  • @Matt Murrell 除法通常是 CPU 上的缓慢操作。 (2认同)
  • 惊人的。让我大吃一惊的是,signum 在图书馆里并不是标准的;这是我所知道的所有其他语言的版本。 (2认同)
  • @JørgenFogh,例如,在AVR中,您只能一位一位地移位;因此,任何较大的移位都将作为循环进行。疼痛。 (2认同)

com*_*orm 186

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

result = copysign(1.0, value) // double
result = copysignf(1.0, value) // float
result = copysignl(1.0, value) // long double
Run Code Online (Sandbox Code Playgroud)

会给你+/- 1.0的结果,具体取决于价值的符号.请注意,浮点零是有符号的:(+ 0)将产生+1,而( - 0)将产生-1.

  • 赞成这一个,投票最受欢迎的答案.令人惊讶的是,SO社区似乎更喜欢黑客使用标准库函数.编程之神可能会谴责大家试图破译不熟悉语言标准的聪明程序员使用的黑客.是的,我知道这将花费我大量的代表,但我宁愿支持暴君而不是其他人... (56认同)
  • 这很接近,但它给出了零的错误答案(至少根据问题中的维基百科文章).不错的建议.+1无论如何. (33认同)
  • 1)各地都没有完全支持C99(考虑VC++); 2)这也是一个C++问题.这是一个很好的答案,但是受欢迎的答案也有效,并且可以更广泛地应用. (10认同)
  • 救主!需要一种确定-0.0和0.0之间的方法 (5认同)
  • 如果你想要一个整数,或者如果你想要零的精确signum结果,我喜欢Mark Byers的答案,这是非常优雅的!如果您不关心上述内容,copysign()可能具有性能优势,具体取决于应用程序 - 如果我正在优化关键循环,我会尝试两者. (4认同)
  • @HighPerformanceMark 这个答案不适用于零。有效、易于阅读的“黑客”比不起作用的标准库函数更好(对于本例) (4认同)
  • 我通常使用标准库函数,但由于最后关于带符号浮点 0 的注释,这确实没有执行所要求的操作。如果您的用例真的希望 sgn(0) 给出 +1 或 - 1,那么这没问题,但我认为大多数寻找 sgn 函数的人都希望它总是给出 0,因为这是通常的数学约定,并且它与其他语言相匹配。 (3认同)
  • 我不会在AVR微控制器上使用`copysign()`,与"hacks"相比,它在程序大小上增加了惊人的334个字节(如果还没有使用`math.h`中的任何其他内容). (2认同)

Cat*_*kul 76

似乎大部分答案都错过了原来的问题.

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

不在标准库中,但存在copysign,也可能是标准的一部分.

    #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);
Run Code Online (Sandbox Code Playgroud)

http://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/utils/sign_functions.html

  • 这应该是最顶级的答案,因为它为问题中提出的问题提供了最接近的解决方案. (5认同)
  • @DavidRector:它还告诉您标准库中没有这样的函数,这是对问题的完整且最终的答案。 (5认同)
  • 我经常得到类似问题的解释是"很容易实现自己"哪个IMO不是一个好理由.它完全掩盖了标准化,不明显的边缘情况以及放置如此广泛使用的工具的问题. (4认同)

Joh*_*ohn 74

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

  • 实际上有一个:http://en.cppreference.com/w/cpp/numeric/math/copysign.使用:std :: copysign(1,x); 请注意,返回类型不是整数. (4认同)
  • @SR_ 你不正确。如果第二个参数为 0.0,`copysign()` 不会使您的第一个参数为 0.0。换句话说,约翰是对的。 (3认同)

xnx*_*xnx 29

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

(x < 0) ? -1 : (x > 0)
Run Code Online (Sandbox Code Playgroud)

  • 缓存未命中?我不知道怎么做.也许你的意思是分支错误预测? (18认同)
  • 你的类型并不快.它会经常导致缓存丢失. (3认同)
  • 在我看来,这将导致整数和布尔类型混淆的警告! (2认同)

chu*_*ica 25

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)
Run Code Online (Sandbox Code Playgroud)

更深的:

该帖子在以下情况下并不具体,x = 0.0, -0.0, +NaN, -NaN.

一个经典的signum()回报+1x>0,-1x>00x==0.

许多答案已经涵盖了这一点,但没有解决x = -0.0, +NaN, -NaN.许多都适用于通常缺少非数字(NaN)和-0.0的整数视点.

典型答案的功能类似于signnum_typical() On -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;
}
Run Code Online (Sandbox Code Playgroud)

相反,建议使用此功能:On -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;
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*ter 17

有一种方法可以不分支,但它不是很漂亮.

sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这意味着“v”是一个不比 int 宽的整数类型 (2认同)

yst*_*sth 11

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

  • 有许多数学应用,其中符号(x)是必要的.否则我只会做`if(x <0)`. (7认同)

Hig*_*ark 5

我在坚果壳中的C副本揭示了一个叫做copysign的标准函数的存在,该函数可能会有用。看来copysign(1.0,-2.0)将返回-1.0,而copysign(1.0,2.0)将返回+1.0。

差不多吧?

  • copysign符合ISO C(C99)和POSIX标准。参见http://www.opengroup.org/onlinepubs/000095399/functions/copysign.html (5认同)
  • lhf说了什么。Visual Studio不是C标准的参考。 (3认同)

Tab*_*kel 5

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

除此之外,我认为关于定义这样一个函数的正确方法的多数观点在某种程度上是正确的,一旦你考虑到两个重要的警告,关于它的"争议"实际上是一个非争论:

  • 正负号函数应该总是返回其操作数的类型,一个类似abs()功能,因为正负号通常用于乘法与后者已经以某种方式处理后的绝对值.因此,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个符号,即使是零值.