#include <bitset>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <type_traits>
using namespace std;
template <typename F, typename I>
enable_if_t<is_floating_point<F>::value && is_unsigned<I>::value, void> func(const I n) {
const auto d = static_cast<F>(n);
auto msb = static_cast<I>(numeric_limits<I>::digits - 1);
while(((static_cast<I>(1) << msb) & n) == static_cast<I>(0)) {
--msb;
}
cout << "Original: " << n << endl << setprecision(50);
if(msb < numeric_limits<F>::digits)
{
cout << "*Lower bound: " << d << "\n*Upper bound: " << d << "\nPrecision loss: " << 0.0 << "\n\n";
}
else
{
const auto zero_based_lost_bits = static_cast<I>(msb - numeric_limits<F>::digits);
const auto one_based_lost_bits = static_cast<I>(msb - numeric_limits<F>::digits + 1);
const auto midway = static_cast<I>(1) << zero_based_lost_bits;
const auto mask = static_cast<I>(1) << one_based_lost_bits;
const auto lost_precision = ~(bitset<numeric_limits<F>::digits>().set().to_ullong() << one_based_lost_bits) & n;
if(midway > lost_precision || midway == lost_precision && (mask & n) == static_cast<I>(0)) {
const auto f = nextafter(d, numeric_limits<F>::infinity());
cout << "*Lower bound: " << d << "\nUpper bound: " << f << "\nPrecision loss: " << lost_precision << "\n\n";
} else {
const auto f = nextafter(d, -numeric_limits<F>::infinity());
cout << "Lower bound: " << f << "\n*Upper bound: " << d << "\nPrecision loss: " << mask - lost_precision << "\n\n";
}
}
}
int main()
{
func<double>(1ULL);
func<double>(9007199254740991ULL);
func<double>(18014398509481982ULL);
func<double>(18014398509481983ULL);
func<double>(9223372036854775295ULL);
func<double>(9223372036854775296ULL);
func<double>(numeric_limits<uint64_t>::max()); // Rounds up
func<double>(numeric_limits<uint64_t>::max() - 2046); // Rounds down
}