我想要一个返回负数为-1和正数为+1的函数。 http://en.wikipedia.org/wiki/Sign_function 编写我自己的代码很容易,但是似乎应该将其存储在标准库中。
编辑:具体来说,我正在寻找一个对浮点数起作用的函数。
我想要一个返回负数为-1和正数为+1的函数。 http://en.wikipedia.org/wiki/Sign_function 编写我自己的代码很容易,但是似乎应该将其存储在标准库中。
编辑:具体来说,我正在寻找一个对浮点数起作用的函数。
Answers:
惊讶的是没有人发布过类型安全的C ++版本:
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
优点:
copysign
是缓慢的,特别是如果您需要提升然后再次缩小。这是无分支的,并且进行了优化注意事项:
在为无符号类型实例化时< 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>());
}
(这是第一个警告的好例子。)
std::copysign
似乎为我带来了出色的代码:4条指令(内联),无分支,完全使用FPU。相比之下,此答案中给出的配方会生成更差的代码(许多指令,包括乘法,在整数单位和FPU之间来回移动)...
copysign
一个int,它将提升为float / double,并且必须在返回时再次变窄。您的编译器可能会优化升级,但是我找不到任何暗示可以保证该标准的内容。另外,要通过copysign实现信号传递,您还需要手动处理0大小写-请确保在任何性能比较中都包括了这种情况。
我不知道它的标准功能。不过,这是一种有趣的编写方式:
(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
“ false”;其他任何值为“ true”;但是,关系和相等运算符始终返回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。
似乎大多数答案都没有回答原始问题。
C / C ++中是否有标准的符号函数(signum,sgn)?
标准库中没有,但是copysign
可以通过几乎相同的方式使用它,copysign(1.0, arg)
并且在中有一个真正的sign函数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,则不会使第一个参数为0.0。换句话说,约翰是正确的。
比以上解决方案更快,包括评分最高的解决方案:
(x < 0) ? -1 : (x > 0)
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<0
与0
上x==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;
}
相反,我提出了此功能:在上-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;
}
有一种方法可以不分支,但不是很漂亮。
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宽的整数类型
通常,C / C ++中没有标准的signum函数,并且缺少这样的基本函数会告诉您很多有关这些语言的信息。
除此之外,我相信关于定义这种功能的正确方法的大多数观点都正确,并且一旦考虑到两个重要的警告,关于该功能的“争论”实际上是毫无争议的:
与函数类似,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)
是要走的路,只是因为他们也极有可能成为分支免费的,另外不需要从铸造整数回到浮点结果值。您可能应该特别指出,由于浮点零值的特殊性,处理时间的考虑,以及因为在浮点算术中接收正确的-1 / +常常非常有用,因此signum的浮点实现不会返回零。 1个符号,即使对于零值也是如此。
我在坚果壳中的C副本揭示了一个叫做copysign的标准函数的存在,该函数可能会有用。看来copysign(1.0,-2.0)将返回-1.0,而copysign(1.0,2.0)将返回+1.0。
差不多吧?
不,它在c ++中不存在,就像在matlab中一样。为此,我在程序中使用了宏。
#define sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
#define sign(x) (((x) > 0) - ((x) < 0))
这也很好。
接受的带有以下过载的答案的确不会触发-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 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
警告,只需使用未命名的参数即可。
有点题外话,但是我用这个:
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);
}
问题很老,但是现在有这种所需的功能。我添加了一个包装,没有,左移和十二月。
您可以使用基于C99的符号位的包装函数,以获取确切的所需行为(请参见下面的代码)。
返回x的符号是否为负。
这也可以应用于无限,NaN和零(如果零是无符号的,则视为正数
#include <math.h>
int signValue(float a) {
return ((!signbit(a)) << 1) - 1;
}
注意:我使用的操作数不是(“!”),因为signbit的返回值未指定为1(即使示例让我们认为它总是这样),但对于负数为true:
返回值
如果x的符号为负,则为非零值(true);否则为false。否则为零(假)。
然后,我乘以2并左移(“ << 1”),这将使我们得到2代表正数,0代表负1,最后减1分别得到正负数的1和-1,分别由OP。
如果可以使用增强,则可以使用boost::math::sign()
from的方法boost/math/special_functions/sign.hpp
。
这是一个分支友好的实现:
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));
}