Handling overflow when casting doubles to integers in C

后端 未结 9 2036
耶瑟儿~
耶瑟儿~ 2020-12-09 08:24

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

相关标签:
9条回答
  • 2020-12-09 09:05

    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

    0 讨论(0)
  • 2020-12-09 09:05

    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.

    0 讨论(0)
  • 2020-12-09 09:10

    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. 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
      if (x < INT_MIN) 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();
      }
    }
    
    0 讨论(0)
  • 2020-12-09 09:10

    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.
    0 讨论(0)
  • 2020-12-09 09:12

    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.

    0 讨论(0)
  • 2020-12-09 09:13

    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.

    0 讨论(0)
提交回复
热议问题