TL;DR use std::llroundl
, assuming the initial value is smaller than 2^64
. If it is bigger, then it wouldn't fit anyway, so up to you to decide what to do with it.
IEEE 754 floating point numbers can represent any integer up to a certain point. If you convert an integer to such a number, and then back to the same integral type, you'll get the exact same value back as long as the integer is not larger than the representable limit.
For double precision numbers, as explained in this answer, that's 2^53
, which is big enough for all 32 bit integers. We can apply the same general formula for for quad precision numbers, and get 2^113
, which is big enough for all 64 bit integers.
So, if long double
on your implementation is a quad precision number, and unsigned long long
is a 64 bit integer, then you can safely do the roundtrip:
unsigned long long i = /* a very big value */;
long double d = i; // safe because it's a widening conversion
assert(static_cast<unsigned long long>(d) == i); // should pass
Now, if you don't have an integer in that long double
, then of course there'll be some loss, because that fraction will have to be cut. If you round the number first, you'll end up with an integer, and that is representable, so no loss (assuming it's not bigger than 2^113
), but of course you'll still end up with a different number than the one you started with before the rounding.
long double d = /* a very big value */;
unsigned long long i = static_cast<unsigned long long>(std::roundl(d));
Or you can avoid the cast altogether by using std::llroundl
:
long double d = /* a very big value */;
unsigned long long i = std::llroundl(d);
Although you'll need to be careful about negative numbers (but that's easy to check beforehand).
All of this assumes that your long double
is never bigger than 2^64
, but if it were, then you wouldn't be able to fit it into the unsigned long long
anyway.