为什么 std::abs(9484282305798401ull) = 9484282305798400?

Why is std::abs(9484282305798401ull) = 9484282305798400?

我目前正在编写一个模板化的辅助方法,它可以将一般的 C 数字(包括 unsigned long long)转换为 GMP 库中的 mpz_class 数字。在这两者之间,调用了 std::abs.

然而,事实证明,对于 C++17 (g++ 6.3.1),

#include <iostream>
#include <cmath>

int main()
{
    std::cout << (unsigned long long)std::abs(9484282305798401ull);
}

给出了错误的输出 9484282305798400

正如我从 cmath 中了解到的,std::abs 首先将参数转换为双精度值。

根据 C++ 文档,double 有 52 个尾数位,这意味着在任何精度损失之前我必须严格小于 2^52 = 4503599627370496 的最大整数值。

我的说法是否正确,因为 9484282305798401 超过了这个限制,std::abs 最终放弃了精度以给出错误的答案?

澄清一下,我完全清楚要求无符号整数的绝对值是完全没有意义的;但是,我希望模板化函数适用于一般的 C 数字,而不是必须分别为每个有符号和无符号类型专门创建一个特化。

首先,脱离上下文(获取无符号类型的绝对值),您所做的事情并没有真正的意义。但是我跑题了。

您发布的代码无法编译。至少在我使用的编译器中没有(repl.it 使用的编译器)。相反,它抱怨模棱两可的过载。即使它确实编译了,它也会将 unsigned long long 转换为不支持其实际值的不同类型(在本例中为 double)。

abs 更改为 llabs,如下所示:

std::cout << (unsigned long long)std::llabs(9484282305798401ull);

..两者都使它编译并产生准确的结果。请参阅整数类型 here.

的不同 abs 函数的文档

你的程序格式错误。来自 [c.math.abs]/29.9.2.3:

If abs() is called with an argument of type X for which is_­unsigned_­v<X> is true and if X cannot be converted to int by integral promotion, the program is ill-formed.

尽管如此,编译器应该警告您这一点。

无论如何,在无符号类型上调用 std::abs 也没有任何意义。

如果您想以不同于标准库函数的方式管理无符号类型,您可以创建自己的 abs 重载:

#include <cmath>
#include <type_traits>

namespace my {

template <class S>
auto abs (S x) -> typename std::enable_if<std::is_signed<S>::value,
                                          decltype(std::abs(x))>::type
{
    return std::abs(x);
}

template <class U>
auto abs (U x) -> typename std::enable_if<std::is_unsigned<U>::value, U>::type
{
    return x;
}

} // namespace my

然后

std::cout << my::abs(9484282305798401ull) << '\n'              // -> 9484282305798401
          << my::abs(-3.14159) << '\n'                         // -> 3.14159
          << my::abs(std::numeric_limits<char>::min()) << '\n' // -> 128
          << my::abs(std::numeric_limits<int>::min()) << '\n'  // -> -2147483648

请注意,std::abs 提升 chars(在我的实现中签名),但由于 ints 的 2 的补码表示,无法检索 INT_MIN.