Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 743
Yiyuan Lee Avatar asked May 31 '17 14:05

Yiyuan Lee


3 Answers

Your program is ill-formed. From [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.

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.

like image 168
Rakete1111 Avatar answered Oct 26 '22 00:10

Rakete1111


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.

like image 22
stelioslogothetis Avatar answered Oct 25 '22 23:10

stelioslogothetis


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 chars (signed in my implementation), but due to 2's complement representation of ints, fails to retrieve the absolute value of INT_MIN.

like image 29
Bob__ Avatar answered Oct 25 '22 23:10

Bob__