I'm currently writing a templated helper method that can convert C numbers in general (including unsigned long long) to mpz_class numbers in the GMP library. In between, there is a call to std::abs
.
However, it turns out that for on C++17 (g++ 6.3.1),
#include <iostream>
#include <cmath>
int main()
{
std::cout << (unsigned long long)std::abs(9484282305798401ull);
}
gives an incorrect output of 9484282305798400
.
As I understood from cmath, std::abs
first casts the argument into a double.
According to the C++ docs, double has 52 mantissa bits, which means that the maximum integer value that I must be strictly less than 2^52 = 4503599627370496
before any loss of precision.
Am I correct to say that since 9484282305798401
exceeds this limit, std::abs
ends up discarding precision to give an incorrect answer?
To clarify, I'm absolutely aware that it makes completely no sense to ask for the absolute value of an unsigned integer; However, I would like the templated function work for general C numbers, instead of having to specifically create a specialization for each signed and unsigned type separately.
Your program is ill-formed. From [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.
Compilers should have to warn you about this though.
It also doesn't really make sense to call std::abs
on an unsigned type anyways.
First off, what you are doing doesn't really make sense out of context (getting the absolute value of an unsigned type). But I digress.
The code you have posted doesn't compile. At least not in the compiler I used (whichever one repl.it uses). Instead it complains of an ambiguous overload. Even if it did compile, it would cast the unsigned long long
to a different type which cannot support its actual value (in this case double
).
Changing abs
to llabs
like so:
std::cout << (unsigned long long)std::llabs(9484282305798401ull);
..both makes it compile and produces an accurate result. See the documentation of the different abs
functions for integer types here.
You can create your own overload of abs
if you want to manage unsigned types in a way that is different for what the standard library function does:
#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
Then
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
Please, note that std::abs
promotes char
s (signed in my implementation), but due to 2's complement representation of int
s, fails to retrieve the absolute value of INT_MIN.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With