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

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

本文关键字:9484282305798400 9484282305798401ull is std abs Why      更新时间:2023-10-16

我目前正在编写一个模板化的帮助程序方法,该方法可以将一般的 C 数(包括无符号长长)转换为 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 个尾数位,这意味着 I 必须严格小于2^52 = 4503599627370496的最大整数值才会有任何精度损失。

我说9484282305798401超过这个限制,std::abs最终会放弃精度给出不正确的答案,这是否正确?

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

您的程序格式不正确。来自 [c.math.abs]/29.9.2.3:

如果使用trueis_­unsigned_­v<X>X类型的参数调用abs(),并且无法通过积分提升将X转换为int,则程序格式不正确。

不过,编译器应该警告您这一点。

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

首先,你正在做的事情在上下文之外并没有真正的意义(获取无符号类型的绝对值)。但我离题了。

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

abs更改为llabs如下所示:

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

..两者都使它编译并产生准确的结果。请参阅此处整数类型的不同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(在我的实现中签名),但由于 2 的补码表示ints,无法检索 INT_MIN 的绝对值。

相关文章:
  • 没有找到相关文章