在 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,例如&lt;cmath&gt;&lt;cstdlib&gt; 当然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 &gt;= 0 ? __x : -__x; 的内联包装器 【参考方案1】:

当您使用来自&lt;cmath&gt;std::abs 和整数类型时,g++(使用 C++11 标准)返回双精度并不奇怪: 来自http://www.cplusplus.com/reference/cmath/abs/:

自 C++11 起,在此标头 (&lt;cmath&gt;) 中为整数类型提供了额外的重载:这些重载在计算之前有效地将 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】:

保证&lt;cmath&gt;/&lt;cstdlib&gt;中存在正确的重载:

C++11,[c.math]:

除了&lt;cstdlib&gt; 中某些数学函数的int 版本之外,C++ 还添加了这些函数的longlong long 重载版本,具有相同的语义。

添加的签名是:

long abs(long);            // labs()
long long abs(long long);  // llabs()

[...]

除了&lt;cmath&gt; 中数学函数的double 版本之外,这些函数的重载版本具有相同的语义。 C++ 添加了这些函数的floatlong double 重载版本,语义相同。

float abs(float);
long double abs(long double);

因此,您应该确保正确包含 &lt;cstdlib&gt;intlonglong long 重载)/&lt;cmath&gt;doublefloatlong double 重载)。

【讨论】:

第 26.8.7-9 节以防万一。 为什么要分离重载?为什么不将它们全部放在一个头文件中? 他们遵循相应 C 函数的位置(absfabs),我不确定这是一个好主意,因为我不止一次遇到不导入的情况&lt;cmath&gt; 并因此使用整数 - 绝对值而不是 FP 值。【参考方案3】:

您不能保证std::abs(x) 将始终为所有算术类型返回|x|。例如,大多数有符号整数实现的负数比正数多一个,因此abs(numeric_limits&lt;int&gt;::min()) 的结果将不等于|x|

【讨论】:

这似乎是唯一的情况,对NaN 来说是安全的。 这就是 OP 命中的情况,因为他将双 -100000000000 转换为其他类型,在许多情况下将值截断为最小值。【参考方案4】:

检查您实际上使用的是来自&lt;cstdlib&gt;std::abs,而不是来自&lt;cmath&gt;std::abs

PS。哦,刚刚看到示例程序,好吧,你去吧,你正在使用std::abs 的浮点重载之一 .

【讨论】:

以上是关于在 std::abs 函数上的主要内容,如果未能解决你的问题,请参考以下文章

由C++绝对值函数想到的

为啥 std::abs() 不能与浮点数一起使用

应用于浮点值时,std::abs 和 std::fabs 之间有啥区别吗?

短整数上的 G++ abs() 似乎将其变成了双精度数?

[Algorithms] 打印菱形的另一种方法

R语言with函数和within函数:with函数基于表达式在dataframe上计算within函数基于表达式在dataframe上计算并修改原始数据