Handling overflow when casting doubles to integers in C
Solution 1:
When casting floats to integers, overflow causes undefined behavior. From the C99 spec, section 6.3.1.4 Real floating and integer:
When a finite value of real floating type is converted to an integer type other than
_Bool
, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.
You have to check the range manually, but don't use code like:
// DON'T use code like this!
if (my_double > INT_MAX || my_double < INT_MIN)
printf("Overflow!");
INT_MAX
is an integer constant that may not have an exact floating-point representation. When comparing to a float, it may be rounded to the nearest higher or nearest lower representable floating point value (this is implementation-defined). With 64-bit integers, for example, INT_MAX
is 2^63 - 1
which will typically be rounded to 2^63
, so the check essentially becomes my_double > INT_MAX + 1
. This won't detect an overflow if my_double
equals 2^63
.
For example with gcc 4.9.1 on Linux, the following program
#include <math.h>
#include <stdint.h>
#include <stdio.h>
int main() {
double d = pow(2, 63);
int64_t i = INT64_MAX;
printf("%f > %lld is %s\n", d, i, d > i ? "true" : "false");
return 0;
}
prints
9223372036854775808.000000 > 9223372036854775807 is false
It's hard to get this right if you don't know the limits and internal representation of the integer and double types beforehand. But if you convert from double
to int64_t
, for example, you can use floating point constants that are exact doubles (assuming two's complement and IEEE doubles):
if (!(my_double >= -9223372036854775808.0 // -2^63
&& my_double < 9223372036854775808.0) // 2^63
) {
// Handle overflow.
}
The construct !(A && B)
also handles NaNs correctly. A portable, safe, but slighty inaccurate version for int
s is:
if (!(my_double > INT_MIN && my_double < INT_MAX)) {
// Handle overflow.
}
This errs on the side of caution and will falsely reject values that equal INT_MIN
or INT_MAX
. But for most applications, this should be fine.
Solution 2:
limits.h
has constants for max and min possible values for integer data types, you can check your double variable before casting, like
if (my_double > nextafter(INT_MAX, 0) || my_double < nextafter(INT_MIN, 0))
printf("Overflow!");
else
my_int = (int)my_double;
EDIT: nextafter()
will solve the problem mentioned by nwellnhof
Solution 3:
To answer your question: The behaviour when you cast out of range floats is undefined or implementation specific.
Speaking from experience: I've worked on a MIPS64 system that didn't implemented these kind of casts at all. Instead of doing something deterministic the CPU threw a CPU exception. The exception handler that ought to emulate the cast returned without doing anything to the result.
I've ended up with random integers. Guess how long it took to trace back a bug to this cause. :-)
You'll better do the range check yourself if you aren't sure that the number can't get out of the valid range.
Solution 4:
A portable way for C++ is to use the SafeInt class:
http://www.codeplex.com/SafeInt
The implementation will allow for normal addition/subtract/etc on a C++ number type including casts. It will throw an exception whenever and overflow scenario is detected.
SafeInt<int> s1 = INT_MAX;
SafeInt<int> s2 = 42;
SafeInt<int> s3 = s1 + s2; // throws
I highly advise using this class in any place where overflow is an important scenario. It makes it very difficult to avoid silently overflowing. In cases where there is a recovery scenario for an overflow, simply catch the SafeIntException and recover as appropriate.
SafeInt now works on GCC as well as Visual Studio
Solution 5:
What is the best way to detect this under/overflow?
Compare the truncated double
to exact limits near INT_MIN,INT_MAX
.
The trick is to exactly convert limits based on INT_MIN,INT_MAX
into double
values. A double
may not exactly represent INT_MAX
as the number of bits in an int
may exceed that floating point's precision.*1 In that case, the conversion of INT_MAX
to double
suffers from rounding. The number after INT_MAX
is a power-of-2 and is certainly representable as a double
. 2.0*(INT_MAX/2 + 1)
generates the whole number one greater than INT_MAX
.
The same applies to INT_MIN
on non-2s-complement machines.
INT_MAX
is always a power-of-2 - 1.INT_MIN
is always:-INT_MAX
(not 2's complement) or-INT_MAX-1
(2's complement)
int double_to_int(double x) {
x = trunc(x);
if (x >= 2.0*(INT_MAX/2 + 1)) Handle_Overflow();
#if -INT_MAX == INT_MIN
if (x <= 2.0*(INT_MIN/2 - 1)) Handle_Underflow();
#else
// Fixed 2022
// if (x < INT_MIN) Handle_Underflow();
if (x - INT_MIN < -1.0) Handle_Underflow();
#endif
return (int) x;
}
To detect NaN and not use trunc()
#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1))
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1))
int double_to_int(double x) {
if (x < DBL_INT_MAXP1) {
#if -INT_MAX == INT_MIN
if (x > DBL_INT_MINM1) {
return (int) x;
}
#else
if (ceil(x) >= INT_MIN) {
return (int) x;
}
#endif
Handle_Underflow();
} else if (x > 0) {
Handle_Overflow();
} else {
Handle_NaN();
}
}
[Edit 2022] Corner error corrected after 6 years.
double
values in the range (INT_MIN - 1.0 ... INT_MIN)
(non-inclusive end-points) convert well to int
. Prior code failed those.
*1 This applies too to INT_MIN - 1
when int
precision is more than double
. Although this is rare, the issues readily applies to long long
. Consider the difference between:
if (x < LLONG_MIN - 1.0) Handle_Underflow(); // Bad
if (x - LLONG_MIN < -1.0) Handle_Underflow();// Good
With 2's complement, some_int_type_MIN
is a (negative) power-of-2 and exactly converts to a double
. Thus x - LLONG_MIN
is exact in the range of concern while LLONG_MIN - 1.0
may suffer precision loss in the subtraction.