views:

262

answers:

2

Double has range more than a 64-bit integer, but its precision is less dues to its representation (since double is 64-bit as well, it can't fit more actual values). So, when representing larger integers, you start to lose precision in the integer part.

#include <boost/cstdint.hpp>
#include <limits>

template<typename T, typename TFloat>
void
maxint_to_double()
{
    T i = std::numeric_limits<T>::max();
    TFloat d = i;
    std::cout
        << std::fixed
        << i << std::endl
        << d << std::endl;
}

int
main()
{
    maxint_to_double<int, double>();
    maxint_to_double<boost::intmax_t, double>();
    maxint_to_double<int, float>();
    return 0;
}

This prints:

2147483647
2147483647.000000
9223372036854775807
9223372036854775800.000000
2147483647
2147483648.000000

Note how max int can fit into a double without loss of precision and boost::intmax_t (64-bit in this case) cannot. float can't even hold an int.

Now, the question: is there a way in C++ to check if the entire range of a given integer type can fit into a loating point type without loss of precision?

Preferably,

  • it would be a compile-time check that can be used in a static assertion,
  • and would not involve enumerating the constants the compiler should know or can compute.
+4  A: 

You can use std::numeric_limits<T>::digits to know how much binary precision you have. e.g:

int binary_digits_double = numeric_limits<double>::digits;       // 53
int binary_digits_long_long = numeric_limits<long long>::digits; // 63
int binary_digits_uint = numeric_limits<unsigned int>::digits;   // 32
AraK
I am using VC, thats why you see `long long` in my answer although it is not standard yet.
AraK
`long long` is also accepted by GCC and ICC. I'm sure DMC and a few others do, as well. It's a *very* common extension.
greyfade
At least it's better than using, say __int64.
Billy ONeal
+6  A: 

Just a little predicate:

#include <limits>

template <typename T, typename U>
struct can_fit
{
    static const bool value = std::numeric_limits<T>::digits
                            <= std::numeric_limits<U>::digits;
};

#include <iostream>

int main(void)
{
    std::cout << std::boolalpha;

    std::cout << can_fit<short, float>::value << std::endl;
    std::cout << can_fit<int, float>::value << std::endl;

    std::cout << can_fit<int, double>::value << std::endl;
    std::cout << can_fit<long long, double>::value << std::endl;

    std::cout << can_fit<short, int>::value << std::endl;
    std::cout << can_fit<int, short>::value << std::endl;
}

Tests if the binary precision available in a T exists in a U. Works on all types.


"Boostified":

// this is just stuff I use
#include <boost/type_traits/integral_constant.hpp>

template <bool B>
struct bool_type : boost::integral_constant<bool, B>
{
    static const bool value = B;
};

typedef const boost::true_type& true_tag;
typedef const boost::false_type& false_tag;

// can_fit type traits
#include <limits>

namespace detail
{
    template <typename T, typename U>
    struct can_fit
    {
        static const bool value = std::numeric_limits<T>::digits
                                <= std::numeric_limits<U>::digits;
    };
}

template <typename T, typename U>
struct can_fit : bool_type<detail::can_fit<T, U>::value>
{
    typedef T type1;
    typedef U type2;

    static const bool value = detail::can_fit<T, U>::value;
};

// test
#include <iostream>

namespace detail
{
    void foo(true_tag)
    {
        std::cout << "T fits in U" << std::endl;
    }

    void foo(false_tag)
    {
        std::cout << "T does not fit in U" << std::endl;
    }
}

// just an example
template <typename T, typename U>
void foo(void)
{
    detail::foo(can_fit<T, U>());
}

int main(void)
{
    foo<int, double>();
}
GMan
+1 for `can_fit`.
AraK