[There are a few questions on this but none of the answers are particularly definitive and several are out of date with the current C++ standard].
My research shows these are the principal methods used to check if a floating point value can be converted to an integral type T
.
if (f >= std::numeric_limits<T>::min() && f <= std::numeric_limits<T>::max() && f == (T)f))
using
std::fmod
to extract the remainder and test equality to 0.using
std::remainder
and test equality to 0.
The first test assumes that a cast from f
to a T
instance is defined. Not true for std::int64_t
to float
, for example.
With C++11, which one is best? Is there a better way?
If your question is "Can I convert this double to int without loss of information?" then I would do something simple like :
Some other options to consider (different compilers / libraries may produce different intrinsic sequences for these tests and be faster/slower):
This is in addition to the tests for overflow you have...
If you are looking to really optimize, you could try the following (works for positive floats, not thoroughly tested): This assumes IEEE 32-bit floats, which are not mandated by the C++ standard AFAIK.
This test is good:
These tests are incomplete:
They both fail to check that the conversion to
T
is defined. Float-to-integral conversions that overflow the integral type result in undefined behaviour, which is even worse than roundoff.I would recommend avoiding
std::fmod
for another reason. This code:compiles (gcc version 4.9.1 20140903 (prerelease) (GCC) on x86_64 Arch Linux using -g -O3 -std=gnu++0x) to this:
The first five instructions implement the range check against
std::numeric_limits<int>::min()
andstd::numeric_limits<int>::max()
. The rest is thefmod
test, accounting for all the misbehaviour of a single invocation of thefprem
instruction (400828..40082d) and some case where a NaN somehow arose.You get similar code by using
remainder
.The problem with:
is that if T is (for example) 64 bits, then the max will be rounded when converting to your usual 64 bit double :-( Assuming 2's complement, the same is not true of the min, of course.
So, depending on the number of bits in the mantisaa, and the number of bits in T, you need to mask off the LS bits of std::numeric_limits::max()... I'm sorry, I don't do C++, so how best to do that I leave to others. [In C it would be something along the lines of
LLONG_MAX ^ (LLONG_MAX >> DBL_MANT_DIG)
-- assuming T islong long int
and f isdouble
and that these are both the usual 64 bit values.]If the T is constant, then the construction of the two floating point values for min and max will (I assume) be done at compile time, so the two comparisons are pretty straightforward. You don't really need to be able to float T... but you do need to know that its min and max will fit in an ordinary integer (long long int, say).
The remaining work is converting the float to integer, and then floating that back up again for the final comparison. So, assuming f is in range (which guarantees (T)f does not overflow):
The alternative seems to be:
as noted elsewhere. Which replaces the floating of
i
byfloor(f)
... which I'm not convinced is an improvement.If f is NaN things may go wrong, so you might want to test for that too.
You could try unpacking
f
withfrexp()
and extract the mantissa as (say) a long long int (withldexp()
and a cast), but when I started to sketch that out it looked ugly :-(Having slept on it, a simpler way of dealing with the max issue is to do:
min <= f < ((unsigned)max+1)
-- ormin <= f < (unsigned)min
-- or(double)min <= f < -(double)min
-- or any other method of constructing -2^(n-1) and +2^(n-1) as floating point values, where n is the number of bits in T.(Serves me right for getting interested in a problem at 1:00am !)
what about converting types like this?
Use
std::fmod(f, 1.0) == 0.0
wheref
is either afloat
,double
, orlong double
. If you're worried about spurious effects of unwanted floating point promotions when usingfloat
s, then use either1.0f
or the more comprehensivestd::fmod(f, static_cast<decltype(f)>(1.0)) == 0.0
which will force, obviously at compile time, the correct overload to be called. The return value of
std::fmod(f, ...)
will be in the range [0, 1) and it's perfectly safe to compare to0.0
to complete your integer check.If it turns out that
f
is an integer, then make sure it's within the permitted range of your chosen type before attempting a cast: else you risk invoking undefined behaviour. I see that you're already familiar withstd::numeric_limits
which can help you here.My reservations against using
std::remainder
are possibly (i) my being a Luddite and (ii) it not being available in some compilers partially implementing the C++11 standard, such as MSVC12. I don't like solutions involving casts since the notation hides that reasonably expensive operation and you need to check in advance for safety. If you must adopt your first choice, at least replace the C-style cast withstatic_cast<T>(f)
;