Can a conversion from double to int be written in portable C

  • A+
Category:Languages

I need to write function like double_to_int(double val, int *err) which would covert double val to integer when it's possible; otherwise report an error (NAN/INFs/OUT_OF_RANGE).

so pseudo code implementation would look like:

if isnan(val):     err = ERR_NAN     return 0 if val < MAX_INT:     err = ERR_MINUS_INF     return MIN_INT if ... return (int)val 

There are at least two similar questions on SO: in this answer it's solved in enough clean way, though it's C++ solution - in C we do not have portable digits for signed int. In this answer, it's explained why we cannot just check (val > INT_MAX || val < INT_MIN).

So the only possible clean way i see is to use floating point environment, but it's stated as implementation-defined feature.

So my question: is there any way to implement double_to_int function in cross-platform way (basing only on C standard, even not considering target platforms to support IEEE-754).?

 


Since conversion of double to int truncates toward zero, all the double values that properly convert to int are in the open interval (−INT_MAX−1, INT_MAX+1), and every value not inside this interval overflows when converted to int or is a NaN. We will find the double value UpperBound that is the greatest representable value less than INT_MAX+1 and the value LowerBound that is the least representable value greater than −INT_MAX−1. Then set of double values in the open interval (−INT_MAX−1, INT_MAX+1) equals set of double values in the closed interval [LowerBound, UpperBound], and we can test whether a value x is in the set by evaluating LowerBound <= x && x <= UpperBound.

The following determines UpperBound:

static double UpperBound;  double b1 = INT_MAX, b0 = nexttoward(b1, 0); if (INT_MAX - (int) ceil(b0) < (int) (b1-b0))     UpperBound = b0; else if (INT_MAX - (int) ceil(b0) == (int) (b1-b0))     UpperBound = nexttoward(ceil(nexttoward(INT_MAX, HUGE_VALL)), 0); else     UpperBound = b1; 

Reasoning:

  • INT_MAX converts to some double b1 that might be less than, equal to, or greater than INT_MAX. In any case, it must (assuming a sane implementation) be one of the values representable in double that equals or surrounds the mathematical value of INT_MAX. (In other words, conversion is not going to skip over a nearby value to give a worse result.) Whichever of those cases is the result, b0 is the next lower representable value, so it must be less than INT_MAX. (If it were greater than INT_MAX, then b1 is not one of the two representable values that surround INT_MAX.)
  • b1 is necessarily an integer; if its significand had bits of position value less than 1, then the significand has room to represent INT_MAX exactly, so the result of converting INT_MAX to double should have been exact.
  • If b0 is not an integer, ceil(b0), INT_MAX, and b1 are all equal. In this case, the middle case of the if-else-if-else is taken. That is discussed below. Otherwise, b0 is an integer, and we continue with the following reasoning.
  • b1-b0 is exact, by Sterbenz’ Lemma. (More than that, they differ by 1 ULP, so that ULP is the result.)
  • Since b0 is an integer less than INT_MAX, it converts to int exactly.
  • Since b1-b0 is an integer less than INT_MAX, it converts to int exactly.
  • If INT_MAX - (int) b0 is less than b1-b0, then b1 is greater than INT_MAX, and so b0 is the largest double less than or equal to INT_MAX. (Note that, since b0 is an integer, INT_MAX - (int) b0 equals INT_MAX - (int) ceil(b0).)
  • If INT_MAX - (int) b0 is greater than than b1-b0, then b1 is less than or equal to INT_MAX, and it is the largest double less than or equal to INT_MAX.

Finally, we consider the case where INT_MAX - (int) ceil(b0) equals b1-b0. In this case, b1 must equal INT_MAX, but there may be additional significand bits below the position value 1. For example, INT_MAX+1 may be a representable value. The reasoning here is:

  • b1 equals INT_MAX. We know this is true either because b0 is an integer (it must be INT_MAX−1) and so the test in the if proves that INT_MAXb0 equals b1b0 or because b0 is not an integer, in which case ceil(b0) must be INT_MAX, which means INT_MAX is representable, so converting it to double yields INT_MAX, and that is how we initialized b1.
  • From INT_MAX, we move to the next lower representable value (which might be either a fraction or some integer amount greater than INT_MAX). Then we apply ceil. If we were at an integer, this has no effect. If we were not at an integer, the result is INT_MAX+1. Then we move to the next lower representable value. If we were at a fraction before, we are now at the greatest representable value less than INT_MAX+1. If we were at an integer before, we are now at INT_MAX, which is also, in this case, the greatest representable value less than INT_MAX+1.

LowerBound can be found from INT_MIN similarly.

The above does require that INT_MAX and INT_MIN be within the range of double. Thus, this could fail in implementation with a large int type and a very constrained non-IEEE-754 double type with no infinities. Of course, in such a system, all conversions from double to int are in range.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: