33

Today, I noticed that when I cast a double that is greater than the maximum possible integer to an integer, I get -2147483648. Similarly, when I cast a double that is less than the minimum possible integer, I also get -2147483648.

Is this behavior defined for all platforms?
What is the best way to detect this under/overflow? Is putting if statements for min and max int before the cast the best solution?

nwellnhof
  • 32,319
  • 7
  • 89
  • 113
  • [Casting minimum 32-bit integer (-2147483648) to float gives positive number (2147483648.0)](http://stackoverflow.com/q/11536389/995714) – phuclv Jul 20 '16 at 15:49

9 Answers9

24

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 ints 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.

nwellnhof
  • 32,319
  • 7
  • 89
  • 113
  • I have just done a little empirical testing, and this answer appears to be correct (again, assuming two's complement integers; if you can't assume that, maybe Boost or SafeInt is the only reasonable way to go). You should upvote this answer and downvote the incorrect answer that advocates my_double > INT_MAX || my_double < INT_MIN; that is in fact incorrect. – bhaller Feb 05 '16 at 00:43
  • 1
    @bhaller I just checked, and both Boost and SafeInt make the same mistake that I discuss in my answer. – nwellnhof Jun 06 '16 at 18:53
  • Yikes. Did you report the problem to them? – bhaller Jun 08 '16 at 22:12
  • 2
    @bhaller Yes, [here](https://svn.boost.org/trac/boost/ticket/12252) and [here](https://safeint.codeplex.com/workitem/17676). – nwellnhof Jun 08 '16 at 22:39
14

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

qrdl
  • 34,062
  • 14
  • 56
  • 86
  • This doesn't work for me for a float. I try and do this `float f = INT_MAX; f++; ConvertToInt(f)` with the limit checking that you have above and it does not overflow. What's different? – Pittfall Aug 11 '14 at 19:58
  • 1
    @Pittfall: `float` has (all except very exotic platforms use IEEE-754 floats) has 24 significant binary digits. So when you set it to INT_MAX, which is 2³¹-1 (INT_MAX on 32-bit platform), the last digit is 128s. So if you add anything smaller than 128, the result is the orignal number, that is `(float)INT_MAX + 1.f == (float)INT_MAX`. With `double`, which has more significant digits than `int` it will work. – Jan Hudec Oct 06 '14 at 14:02
  • `INT_MAX` and `INT_MIN` are the C way of checking. C++ way is using [`std::numeric_limits::max()`](http://en.cppreference.com/w/cpp/types/numeric_limits/max) and `…::min()`. – Jan Hudec Oct 06 '14 at 14:04
  • 10
    This answer is wrong, because it's not guaranteed that `INT_MIN` and `INT_MAX` have precise floating point representations. With 64-bit integers for example, `INT_MAX` is 2^63-1 and `(double)INT_MAX` will be rounded to 2^63, so this check won't detect an overflow if `my_double` is 2^63. Changing the check to `my_double >= INT_MAX || my_double < INT_MIN` should actually work with two's complement integers, even if it looks wrong. – nwellnhof May 24 '15 at 13:37
  • 1
    I agree that this answer is wrong. It should be removed. – bhaller Feb 05 '16 at 00:45
  • 3
    The only problem with `nextafter` is that a `double` that equals `INT_MIN` will be detected as overflow, although it could be converted to `int`. Other than that, it's a nice, portable solution. – nwellnhof Jun 06 '16 at 18:43
12

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.

Nils Pipenbrinck
  • 83,631
  • 31
  • 151
  • 221
4

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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • You say that a power of 2, the size of `MAX_INT+1`, is certainly representable as a `double`. Can you explain why? What are your assumptions? Is assuming IEEE enough? – Kristian Spangsege Oct 15 '17 at 02:31
  • 1
    Ok, I can see now that you are assuming that `FLT_RADIX` is 2 and that `INT_MAX` is well below `DBL_MAX`, and nothing else beyond what is guaranteed by the language standard. That's a wonderful and creative solution. I love it. – Kristian Spangsege Oct 15 '17 at 03:18
  • One comment: What you call *underflow*, I believe is formally called *negative overflow*. Underflow is when result snaps to zero. Overflow is when magnitude is impacted, roughly speaking. – Kristian Spangsege Oct 15 '17 at 05:55
  • 1
    @KristianSpangsege In C, _underflow_ with FP matches the [comment](https://stackoverflow.com/questions/526070/handling-overflow-when-casting-doubles-to-integers-in-c/38470183?noredirect=1#comment80450100_38470183) and C uses _overflow_ for results outside the `int` range (+ and -). In the vernacular, I have heard and read _underflow_ used with excessively negative results and since OP used it that way, made sense to answer likewise. _negative overflow_ is a good term - even if a bit verbose. – chux - Reinstate Monica Oct 15 '17 at 06:07
  • Strictly speaking, there is one more assumption, namely that `DBL_MANT_DIG <= DBL_MAX_EXP` such that the result of `ceil()` is always representable. IEEE types satisfy this condition. I picked this up from the Linux man page for `ceil()`. – Kristian Spangsege Oct 15 '17 at 06:39
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

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • A rough test led me to believe it's probably impossible to detect overflow in C++ (without serious overhead or total change of paradigm, such as wrapping every integer as object). This _dedicated class_ can't seem to handle `SafeInt x = std::numeric_limits::max() + 100` (it doesn't throw). – kizzx2 Dec 02 '10 at 13:02
  • 2
    are you kidding...? your right hand side *did* already overflow before it even reaches the SafeInt ctor. You can't blame SafeInt for that – Ichthyo Jan 08 '11 at 00:02
  • 1
    SafeInt makes the same mistake that I discuss in my answer. `SafeInt v = pow(2.0, 63.0)` doesn't throw. – nwellnhof Jun 06 '16 at 18:36
2

We meet the same question. such as:

double d = 9223372036854775807L;
int i = (int)d;

in Linux/window, i = -2147483648. but In AIX 5.3 i = 2147483647.

If the double is outside the range of interger.

  • Linux/window always return INT_MIN.
  • AIX will return INT_MAX if double is postive, will return INT_MIN of double is negetive.
Dong Wang
  • 21
  • 1
  • 1
    Linux definitely does not always return INT_MIN, and I'd be surprised if Windows did, because this is a function of the processor architecture itself (it isn't operating system specific). – al45tair Jun 04 '19 at 10:01
  • `printf("%d\n", (int) (double) (9223372036854775807L));`prints 2147483647, but `double d = 9223372036854775807L; int i = (int) d; printf("%d", i);` prints -2147483648. MinGW730_64 on Windows – Xixiaxixi Oct 14 '21 at 12:30
2

Another option is to use boost::numeric_cast which allows for arbitrary conversion between numerical types. It detects loss of range when a numeric type is converted, and throws an exception if the range cannot be preserved.

The website referenced above also provides a small example which should give a quick overview on how this template can be used.

Of course, this isn't plain C anymore ;-)

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
fhe
  • 6,099
  • 1
  • 41
  • 44
  • boost::numeric_cast makes the same mistake that I discuss in my answer. `numeric_cast(pow(2.0, 63.0))` doesn't throw. – nwellnhof Jun 06 '16 at 18:52
0

I am not sure about this but I think it may be possible to "turn on" floating point exceptions for under/overflow...take a look at this Dealing with Floating-point Exceptions in MSVC7\8 so you might have an alternative to if/else checks.

Sandeep Datta
  • 28,607
  • 15
  • 70
  • 90
-1

I can't tell you for certain whether it is defined for all platforms, but that is pretty much what's happened on every platform I've used. Except, in my experience, it rolls. That is, if the value of the double is INT_MAX + 2, then when the result of the cast ends up being INT_MIN + 2.

As for the best way to handle it, I'm really not sure. I've run up against the issue myself, and have yet to find an elegant way to deal with it. I'm sure someone will respond that can help us both there.

Jeff Barger
  • 1,241
  • 1
  • 13
  • 19