我想要一个函数,它为负数返回-1,为正数返回+1。 http://en.wikipedia.org/wiki/Sign_function 编写自己的代码很容易,但它似乎应该在某个标准库中。
编辑:具体来说,我正在寻找一个在浮点数上工作的函数。
类型安全的 C++ 版本:
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
好处:
实际上实现了符号(-1、0 或 1)。此处使用 copysign 的实现仅返回 -1 或 1,这不是符号。此外,这里的一些实现返回一个浮点数(或 T)而不是一个 int,这似乎很浪费。
适用于整数、浮点数、双精度数、无符号短裤或任何可从整数 0 构造且可排序的自定义类型。
快速地! copysign 很慢,特别是如果您需要先推广然后再缩小范围。这是无分支的,并且优化得很好
符合标准! bitshift hack 很简洁,但仅适用于某些位表示,并且在您具有无符号类型时不起作用。在适当的时候,它可以作为手动专业化提供。
准确的!与零的简单比较可以保持机器内部的高精度表示(例如 x87 上的 80 位),并避免过早舍入为零。
注意事项:
它是一个模板,因此在某些情况下编译可能需要更长的时间。
显然,有些人认为使用一个新的、有点深奥、非常慢的标准库函数甚至没有真正实现 signum 更容易理解。
当为无符号类型实例化时,检查的 < 0 部分会触发 GCC 的 -Wtype-limits 警告。您可以通过使用一些重载来避免这种情况: template
我不知道它的标准功能。不过,这是一种有趣的编写方式:
(x > 0) - (x < 0)
这是一种更易读的方法:
if (x > 0) return 1;
if (x < 0) return -1;
return 0;
如果你喜欢三元运算符,你可以这样做:
(x > 0) ? 1 : ((x < 0) ? -1 : 0)
x==0
给出了不正确的结果。
<
, >
... 应产生 1,如果为假,则应产生 0”
0
的值为“假”;任何其他值为“真”;但是,关系和相等运算符总是返回 0
或 1
(参见标准 6.5.8 和 6.5.9)。 -- 表达式 a * (x == 42)
的值是 0
或 a
。
copysign
用于积分 x
。
有一个名为 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。
copysign()
,与“hacks”相比,它增加了惊人的 334 字节的程序大小(如果尚未使用 math.h
中的任何其他内容)。
似乎大多数答案都错过了原始问题。
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);
显然,原始海报问题的答案是否定的。没有标准 C++ sgn
函数。
copysign()
将不会使您的第一个参数为 0.0。换句话说,约翰是正确的。
C/C++ 中是否有标准的符号函数(signum、sgn)?
是的,取决于定义。
C99 及更高版本在 <math.h>
中具有 signbit()
宏
int 符号位(实浮点 x);当且仅当其参数值的符号为负时,signbit 宏才会返回非零值。 C11 §7.12.3.6
然而 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
。许多都适用于通常缺少非数字 (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;
}
比上述解决方案更快,包括评分最高的解决方案:
(x < 0) ? -1 : (x > 0)
有一种无需分支的方法,但它不是很漂亮。
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
http://graphics.stanford.edu/~seander/bithacks.html
该页面上还有许多其他有趣,过于聪明的东西......
sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
或 sign = (v > 0) - (v < 0);
。
v
是不比 int 宽的整数类型
如果您只想测试符号,请使用 signbit(如果其参数有负号则返回真)。不知道您为什么特别希望返回 -1 或 +1; copysign 对此更方便,但听起来它会在某些平台上为负零返回 +1,仅部分支持负零,其中符号位可能会返回 true。
if (x < 0)
。
一般来说,C/C++ 中没有标准的符号函数,缺少这样一个基本函数可以告诉你很多关于这些语言的信息。
除此之外,我相信大多数关于定义这种函数的正确方法的观点都是正确的,一旦你考虑到两个重要的警告,关于它的“争议”实际上是一个非争论:
符号函数应始终返回其操作数的类型,类似于 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 符号,即使是零值。
我的 C in a Nutshell 副本揭示了一个名为 copysign 的标准函数的存在,它可能很有用。看起来 copysign(1.0, -2.0) 会返回 -1.0,而 copysign(1.0, 2.0) 会返回 +1.0。
很接近吧?
下面重载的公认答案确实不会触发 -Wtype-limits。但它确实会触发 unused argument 警告(在 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 上触发任何警告。
-Wunused-parameter
警告,只需使用未命名的参数。
不,它在 c++ 中不存在,就像在 matlab 中一样。为此,我在我的程序中使用了一个宏。
#define sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
#define sign(x) (((x) > 0) - ((x) < 0))
这也很好。
这个问题很老,但现在有这种所需的功能。我添加了一个带有 not、left shift 和 dec 的包装器。
您可以使用基于 signbit from C99 的包装函数来获得所需的确切行为(请参阅下面的代码)。
返回 x 的符号是否为负。这也可以应用于无穷大、NaN 和零(如果零是无符号的,则认为它是正数)
#include <math.h>
int signValue(float a) {
return ((!signbit(a)) << 1) - 1;
}
注意:我使用操作数而不是(“!”),因为符号位的返回值未指定为 1(即使示例让我们认为它总是这样)但对于负数是正确的:
返回值 如果 x 的符号为负,则返回非零值 (true);否则为零(假)。
然后我用左移乘以 2(“<< 1”),这将给我们 2 表示正数和 0 表示负数,最后减 1 以获得 1 和 -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 可用,您可以使用 boost/math/special_functions/sign.hpp
中的 boost::math::sign()
方法。
虽然接受的答案中的整数解决方案非常优雅,但它无法为 double 类型返回 NAN 让我很困扰,所以我稍微修改了它。
template <typename T> double sgn(T val) {
return double((T(0) < val) - (val < T(0)))/(val == val);
}
请注意,返回浮点 NAN 而不是硬编码的 NAN
会导致在 some implementations 中设置符号位,因此 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));
}
除非您的数据有一半的数字为零,否则分支预测器将选择其中一个分支作为最常见的分支。两个分支都只涉及简单的操作。
或者,在某些编译器和 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 。
int sign(float n)
{
union { float f; std::uint32_t i; } u { n };
return 1 - ((u.i >> 31) << 1);
}
该函数假定:
浮点数的 binary32 表示
使用命名联合时对严格别名规则产生异常的编译器
copysign
;如果您使用的是 static_assert
,那么您已经获得了 C++11,还不如真正使用 copysign
。
double signof(double a) { return (a == 0) ? 0 : (a<0 ? -1 : 1); }
当您可以简单地执行此操作时,为什么要使用三元运算符和 if-else
#define sgn(x) x==0 ? 0 : x/abs(x)
x == INT_MIN
时可能出现未定义的行为。
不定期副业成功案例分享
std::copysign
对我来说似乎是 优秀 代码:4 条指令(内联),没有分支,完全使用 FPU。相比之下,这个答案中给出的配方会生成更糟糕的代码(更多的指令,包括乘法,在整数单元和 FPU 之间来回移动)......copysign
,它会提升为 float/double,并且必须在返回时再次缩小。您的编译器可能会优化该促销,但我找不到任何表明标准保证的内容。此外,要通过 copysign 实现 signum,您需要手动处理 0 案例 - 请确保将其包含在任何性能比较中。