为什么 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
提升 char
s(在我的实现中签名),但由于 int
s 的 2 的补码表示,无法检索 INT_MIN.
我目前正在编写一个模板化的辅助方法,它可以将一般的 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 typeX
for whichis_unsigned_v<X>
istrue
and ifX
cannot be converted toint
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
提升 char
s(在我的实现中签名),但由于 int
s 的 2 的补码表示,无法检索 INT_MIN.