在 std::abs 函数上
Posted
技术标签:
【中文标题】在 std::abs 函数上【英文标题】:On the std::abs function 【发布时间】:2012-11-07 18:50:33 【问题描述】:std::abs()
函数是否为 C++11 中的所有算术类型定义良好,并且将返回 |x|
而没有近似问题?
奇怪的是,使用 g++4.7、std::abs(char)
、std::abs(short int)
、std::abs(int)
、std::abs(long int)
和 std::abs(long long int)
似乎返回一个双精度值(相反:http://en.cppreference.com/w/cpp/numeric/math/abs)。如果将数字转换为双精度数,对于非常大的数字(如 -9223372036854775806LL = 2^63-3
),我们可能会有一些近似误差。
那么我能保证std::abs(x)
将始终为所有算术类型返回|x|
吗?
编辑:这是一个进行一些测试的示例程序
#include <iostream>
#include <iomanip>
#include <cmath>
#include <typeinfo>
template<typename T>
void abstest(T x)
static const unsigned int width = 16;
const T val = x;
if (sizeof(val) == 1)
std::cout<<std::setw(width)<<static_cast<int>(val)<<" ";
std::cout<<std::setw(width)<<static_cast<int>(std::abs(val))<<" ";
else
std::cout<<std::setw(width)<<val<<" ";
std::cout<<std::setw(width)<<static_cast<T>(std::abs(val))<<" ";
std::cout<<std::setw(width)<<sizeof(val)<<" ";
std::cout<<std::setw(width)<<sizeof(std::abs(val))<<" ";
std::cout<<std::setw(width)<<typeid(val).name()<<" ";
std::cout<<std::setw(width)<<typeid(std::abs(val)).name()<<std::endl;
int main()
double ref = -100000000000;
abstest<char>(ref);
abstest<short int>(ref);
abstest<int>(ref);
abstest<long int>(ref);
abstest<long long int>(ref);
abstest<signed char>(ref);
abstest<signed short int>(ref);
abstest<signed int>(ref);
abstest<signed long int>(ref);
abstest<signed long long int>(ref);
abstest<unsigned char>(ref);
abstest<unsigned short int>(ref);
abstest<unsigned int>(ref);
abstest<unsigned long int>(ref);
abstest<unsigned long long int>(ref);
abstest<float>(ref);
abstest<double>(ref);
abstest<long double>(ref);
return 0;
【问题讨论】:
是什么让您认为 g++ 实现返回双精度值?也许你可以提供一个你正在做的事情的样本,表明你正在返回一个双精度值? 请注意,在不同的标头中有几个std::abs
,例如<cmath>
和<cstdlib>
。
当然std::abs(x)
返回|x|
。也许您想知道decltype(std::abs(x))
是否会匹配decltype(x)
?我对您所说的“std::abs(x) 总是返回 |x| 吗?”的确切含义有点困惑
我不知道 C++ 标准是怎么说的,但是这样的保证肯定是不可能的,因为每当 int
是一个二进制补码有符号整数时,可能的最小值 int
的绝对值不能表示为 int
。 (例如,如果我们有 32 位整数,那么最小可能值为 -2,147,483,648,但最大可能值仅为 2,147,483,647。)
我不知道你的 gcc 4.7,但我的 gcc 4.7 调用 __gnu_cxx::abs
,它是 __x >= 0 ? __x : -__x;
的内联包装器
【参考方案1】:
当您使用来自<cmath>
的std::abs
和整数类型时,g++(使用 C++11 标准)返回双精度并不奇怪:
来自http://www.cplusplus.com/reference/cmath/abs/:
自 C++11 起,在此标头 (
<cmath>
) 中为整数类型提供了额外的重载:这些重载在计算之前有效地将 x 强制转换为双精度(定义为 T 为任何整数类型)。
这实际上是在/usr/include/c++/cmath
中实现的:
template<typename _Tp>
inline _GLIBCXX_CONSTEXPR
typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
double>::__type
abs(_Tp __x)
return __builtin_fabs(__x);
【讨论】:
【参考方案2】:保证<cmath>
/<cstdlib>
中存在正确的重载:
C++11,[c.math]:
除了
<cstdlib>
中某些数学函数的int
版本之外,C++ 还添加了这些函数的long
和long long
重载版本,具有相同的语义。添加的签名是:
long abs(long); // labs() long long abs(long long); // llabs()
[...]
除了
<cmath>
中数学函数的double
版本之外,这些函数的重载版本具有相同的语义。 C++ 添加了这些函数的float
和long double
重载版本,语义相同。float abs(float); long double abs(long double);
因此,您应该确保正确包含 <cstdlib>
(int
、long
、long long
重载)/<cmath>
(double
、float
、long double
重载)。
【讨论】:
第 26.8.7-9 节以防万一。 为什么要分离重载?为什么不将它们全部放在一个头文件中? 他们遵循相应 C 函数的位置(abs
和 fabs
),我不确定这是一个好主意,因为我不止一次遇到不导入的情况<cmath>
并因此使用整数 - 绝对值而不是 FP 值。【参考方案3】:
您不能保证std::abs(x)
将始终为所有算术类型返回|x|
。例如,大多数有符号整数实现的负数比正数多一个,因此abs(numeric_limits<int>::min())
的结果将不等于|x|
。
【讨论】:
这似乎是唯一的情况,对NaN
来说是安全的。
这就是 OP 命中的情况,因为他将双 -100000000000 转换为其他类型,在许多情况下将值截断为最小值。【参考方案4】:
检查您实际上使用的是来自<cstdlib>
的std::abs
,而不是来自<cmath>
的std::abs
。
PS。哦,刚刚看到示例程序,好吧,你去吧,你正在使用std::abs
的浮点重载之一
.
【讨论】:
以上是关于在 std::abs 函数上的主要内容,如果未能解决你的问题,请参考以下文章
应用于浮点值时,std::abs 和 std::fabs 之间有啥区别吗?
R语言with函数和within函数:with函数基于表达式在dataframe上计算within函数基于表达式在dataframe上计算并修改原始数据