what is the safe way to convert double to int?

1.7k Views Asked by At

I have been given a legacy code, where someone(s) have carelessly assigned double values to int variables like:

int a = 10;
double b = 20;
a = b;

Now to get rid of the

warning C4244: '=': conversion from 'double' to 'int', possible loss of data

warnings, I tried editing the code upstream and get rid of the unnecessary double variables, but it turns out to be too messy!

I could also use casting:

a = (int) b;

but actually there is no guarantee that b will be within the limits of an integer. I thought of making a helper function:

int safeD2I(double inputVar){
  if ((inputVar < INT_MAX) && (INT_MIN < inputVar)){
    return (int) inputVar;
  } else {
    exit(-1);
  }
}

but I'm not sure if this is the best implementation. I was wondering if there is a more canonical way to handle this issue?

what I want:

  • in the case the b variable being outside of the integer bounds, the program stops immediately
  • prints an error message to the terminal indicating that the specific line and time when this issue happened.

Thanks for your support in advance.

2

There are 2 best solutions below

0
On

what is the safe way to convert double to int?

if ((inputVar < INT_MAX) && (INT_MIN < inputVar)){ fails edge cases.

Wrong edges as it is more like, but not exactly like (inputVar < INT_MAX + 1) && (INT_MIN - 1 < inputVar)

Be wary of code like some_FP < SOME_INT_MAX as SOME_INT_MAX may not convert to the FP value needed due to the integer type may have more precision that the FP one. This is not usually a problem with int, double.


Test if the double is within the range of (INT_MIN-1 .... INT_MAX+1)1. Note () and not [].

If not, error out or handle in some defined way of your choosing.

Assuming typical 2's complement, but not assuming the precision of double exceeds int (more useful to migrate code to float, long long that way), some sample code:

// FP version of INT_MAX + 1.0 
// Avoid direct (INT_MAX + 1.0) as that can have precision woes
#define DBL_INTMAX_P1 ((INT_MAX/2 + 1)*2.0)

int X_int_from_double(double x) {
  // Coded to insure NAN fails the if()
  if (!(x - INT_MIN > -1.0 && x < DBL_INTMAX_P1)) {
    errno = ERANGE;
    fprintf(stderr, "Error in %s,  %.*e too large\n", __func__, DBL_DECIMAL_DIG - 1, x);

    exit(EXIT_FAILURE);
    // or additional code to handle conversion in some specified manner
    // Example: assuming "wrap"
    if (!isfinite(x)) {
      if (!isnan(x)) return 0;
      if (x > 0) return INT_MAX;
      else return INT_MIN; 
    }
    modf(x, &x); // drop fraction
    x = fmod(x, DBL_INTMAX_P1*2);
    if (x >= DBL_INTMAX_P1) x -= DBL_INTMAX_P1*2;
    else if (x < -DBL_INTMAX_P1) x += DBL_INTMAX_P1*2;
  }
  return (int) x;
}

To record the line where this failed, consider a macro to pass the line number.

int X_int_from_double(double x, unsigned);
#define DOUBLE_TO_INT(x) X_int_from_double((x), __LINE__)

1 Example -2,147,483,648.9999... to 2,147,483,647.9999...

5
On

First, there's nothing inherently wrong or unsafe with the code as-is. Conversion by assignment is a completely legitimate part of the C language. If this is legacy code where you don't know if it's being done safely or not, you need to do the analysis to determine that.

If you find that you do need to catch possible out-of-bounds values (which produce undefined behavior (!!) when converted to int), your code is both wrong and fragile. Comparisons like:

double x;
...
if (x < INT_MAX) ...

coerce INT_MAX to type double for the comparison. In practice in a world where double is IEEE double and int is 32-bit, this happens to be safe, but it would be unsafe for example if you changed double to float, since a 32-bit INT_MAX is not representable in single-precision float. The value will be rounded, and then the comparison takes place after the rounding.

Now, it turns out you also have an off-by-one error (<= INT_MAX, not < INT_MAX, is what's in-bounds) as well as incorrect logic (|| instead of &&) so the rounding would actually fix part of that. But it's not right to depend on it. Instead you need to manufacture a power of two you can compare against, so it's safe to convert to floating point. For example:

  • if (x < 2.0*(INT_MAX/2+1) && x >= INT_MIN)
  • if (-x > (-INT_MAX-1) && x >= INT_MIN)
  • if (-x > INT_MIN && x >= INT_MIN)

These all assume INT_MIN is actually a power of two (full range twos complement) which is a reasonable real-world assumption and required by C2x+. If you want more generality it's more work.

Finally, I first wrote this as a comment on your question, but the more I think about it, it really belongs in an answer: Based on what your C proficiency seems to be, I would be very wary of making any unnecessary or "cleanup" changes to this code. You are likely to break far more than you fix. Make changes only where you've done analysis to determine that there's an active bug, and don't refactor things or change types to fix. Make simple direct inline fixes.