Let's say I've got a function that accepts a 64-bit integer, and I want to call
it with a double
with arbitrary numeric value (i.e. it may be very large in
magnitude, or even infinite):
void DoSomething(int64_t x);
double d = [...];
DoSomething(d);
Paragraph 1 of [conv.fpint] in the C++11 standard says this:
A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion trun- cates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.
Therefore there are many values of d
above that will cause undefined
behavior. I would like conversion to saturate, so that values greater than
std::numeric_limits<int64_t>::max()
(called kint64max
below), including
infinity, become that value, and similarly with the minimum representable
value. This seems the natural approach:
double clamped = std::min(d, static_cast<double>(kint64max));
clamped = std::max(clamped, static_cast<double>(kint64min));
DoSomething(clamped);
But, the next paragraph in the standard says this:
A prvalue of an integer type or of an unscoped enumeration type can be converted to a prvalue of a floating point type. The result is exact if possible. If the value being converted is in the range of values that can be represented but the value cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value.
So clamped
may still wind up being kint64max + 1
, and behavior may still be
undefined.
What is the simplest portable way to do what I'm looking for? Bonus points if
it also gracefully handles NaN
s.
Update: To be more precise, I would like the following to all be true of an
int64_t SafeCast(double)
function that solves this problem:
For any double
d
, callingSafeCast(d)
does not perform undefined behavior according to the standard, nor does it throw an exception or otherwise abort.For any double
d
in the range[-2^63, 2^63)
,SafeCast(d) == static_cast<int64_t>(d)
. That is,SafeCast
agrees with C++'s conversion rules wherever the latter is defined.For any double
d >= 2^63
,SafeCast(d) == kint64max
.For any double
d < -2^63
,SafeCast(d) == kint64min
.
I suspect the true difficulty here is in figuring out whether d
is in the
range [-2^63, 2^63)
. As discussed in the question and in comments to other
answers, I think using a cast of kint64max
to double
to test for the upper
bound is a non-starter due to undefined behavior. It may be more promising to
use std::pow(2, 63)
, but I don't know whether this is guaranteed to be exactly
2^63.
static_cast
kint64max + 1ULL
(or(uint64_t) 1
), which should be exactly representable, and then usestd::nextafter
to get the previous representable value, and clamp down to that. – ContributionNaN
andInfinity
? – Jelenemin
andmax
will work for Infinity but not NaN. You need separate testing for that case, and it's unclear what should be returned. – Jerri