20

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).?

17
  • "Please read before marking as duplicate." should go on comment section Commented Jun 29, 2018 at 15:26
  • 1
    I wonder if frexp is any help. Commented Jun 29, 2018 at 15:30
  • 1
    I really feel like your question is answer by the answer you linked in your question, thus make your question a duplicate. Commented Jun 29, 2018 at 15:33
  • You really should explain why the answer shown by Stargateur does not answer your question. Commented Jun 29, 2018 at 15:35
  • I feel that all the "close duplicates" fail over to a particular implementation at some point in their answers. I strongly believe that it's not possible to do this; my answer is little more than an invitation to peer review. Commented Jun 29, 2018 at 15:35

7 Answers 7

3
+200

[This answer has been edited with a completely new approach.]

This approach uses the definition of floating-point formats in the C standard—as a signed base-b numeral multiplied by a power of b. Knowing the number of digits in the significand (provided by DBL_MANT_DIG) and the exponent limit (provided by DBL_MAX_EXP) allows us to prepare exact double values as end points.

I believe it will work in all conforming C implementations subject to the modest additional requirements stated in the initial comment.

/* This code demonstrates safe conversion of double to int in which the input double is converted to int if and only if it is in the supported domain for such conversions (the open interval (INT_MIN-1, INT_MAX+1)). If the input is not in range, an error is indicated (by way of an auxiliary argument) and no conversion is performed, so all behavior is defined. There are a few requirements not fully covered by the C standard. They should be uncontroversial and supported by all reasonable C implementations: Conversion of an int that is representable in double produces the exact value. The following operations are exact in floating-point: Dividing by the radix of the floating-point format, within its range. Multiplying by +1 or -1. Adding or subtracting two values whose sum or difference is representable. FLT_RADIX is representable in int. DBL_MIN_EXP is not greater than -DBL_MANT_DIG. (The code can be modified to eliminate this requirement.) Deviations from the requested routine include: This code names the routine DoubleToInt instead of double_to_int. The only error indicated is ERANGE. Code to distinguish the error more finely, such as providing separate values for NaNs, infinities, and out-of-range finite values, could easily be added. */ #include <float.h> #include <errno.h> #include <limits.h> #include <stdio.h> /* These values will be initialized to the greatest double value not greater than INT_MAX+1 and the least double value not less than INT_MIN-1. */ static double UpperBound, LowerBound; /* Return the double of the same sign of x that has the greatest magnitude less than x+s, where s is -1 or +1 according to whether x is negative or positive. */ static double BiggestDouble(int x) { /* All references to "digits" in this routine refer to digits in base FLT_RADIX. For example, in base 3, 77 would have four digits (2212). In this routine, "bigger" and "smaller" refer to magnitude. (3 is greater than -4, but -4 is bigger than 3.) */ // Determine the sign. int s = 0 < x ? +1 : -1; // Count how many digits x has. int digits = 0; for (int t = x; t; ++digits) t /= FLT_RADIX; /* If the double type cannot represent finite numbers this big, return the biggest finite number it can hold, with the desired sign. */ if (DBL_MAX_EXP < digits) return s*DBL_MAX; // Determine whether x is exactly representable in double. if (DBL_MANT_DIG < digits) { /* x is not representable, so we will return the next lower representable value by removing just as many low digits as necessary. Note that x+s might be representable, but we want to return the biggest double less than it, which is also the biggest double less than x. */ /* Figure out how many digits we have to remove to leave at most DBL_MANT_DIG digits. */ digits = digits - DBL_MANT_DIG; // Calculate FLT_RADIX to the power of digits. int t = 1; while (digits--) t *= FLT_RADIX; return x / t * t; } else { /* x is representable. To return the biggest double smaller than x+s, we will fill the remaining digits with FLT_RADIX-1. */ // Figure out how many additional digits double can hold. digits = DBL_MANT_DIG - digits; /* Put a 1 in the lowest available digit, then subtract from 1 to set each digit to FLT_RADIX-1. (For example, 1 - .001 = .999.) */ double t = 1; while (digits--) t /= FLT_RADIX; t = 1-t; // Return the biggest double smaller than x+s. return x + s*t; } } /* Set up supporting data for DoubleToInt. This should be called once prior to any call to DoubleToInt. */ static void InitializeDoubleToInt(void) { UpperBound = BiggestDouble(INT_MAX); LowerBound = BiggestDouble(INT_MIN); } /* Perform the conversion. If the conversion is possible, return the converted value and set *error to zero. Otherwise, return zero and set *error to ERANGE. */ static int DoubleToInt(double x, int *error) { if (LowerBound <= x && x <= UpperBound) { *error = 0; return x; } else { *error = ERANGE; return 0; } } #include <string.h> static void Test(double x) { int error, y; y = DoubleToInt(x, &error); printf("%.99g -> %d, %s.\n", x, y, error ? strerror(error) : "No error"); } #include <math.h> int main(void) { InitializeDoubleToInt(); printf("UpperBound = %.99g\n", UpperBound); printf("LowerBound = %.99g\n", LowerBound); Test(0); Test(0x1p31); Test(nexttoward(0x1p31, 0)); Test(-0x1p31-1); Test(nexttoward(-0x1p31-1, 0)); } 
Sign up to request clarification or add additional context in comments.

8 Comments

@NominalAnimal: An interesting idea, I will think about whether converting to unsigned int gives us some leeway. However, the conversion caused by a cast from double to unsigned int is not necessarily modulo. Per C 2011 (N1570) 6.3.1.4 note 61, “The remaindering operation performed when a value of integer type is converted to unsigned type need not be performed when a value of real floating type is converted to unsigned type. Thus, the range of portable real floating values is (−1, Utype_MAX+1).”
limits.h defines INT_MAX and INT_MIN.
@BobJarvis: What is your point? We do not have any issue with obtaining INT_MAX or INT_MIN in int. The problem is we do not know they can be converted to double without error, but we need to find the greatest double that is less than INT_MAX+1. So we need to find some way to evade or correct for the rounding errors that may occur during conversion.
@EricPostpischil: I rewrote my answer to use floor(max_double_to_int) == (double)INT_MAX and ceil(min_double_to_int) == (double)INT_MIN in the nextafter() loops, plus handling for the odd case when DBL_MAX <= INT_MAX or -DBL_MAX >= INT_MIN via strtod().
The request was for "portable C", which raises the question of "Which one?" AFAIK, nexttoward(..) first appeared in C99; it's not in ANSI C. Can nexttoward(..) be written in ANSI C?
|
2

The answer to "Can a conversation from double to int be written in portable C" is clearly "yes".

For example, you could sprintf the floating value to a string, do string-based inspection (i.e. by string-based comparison to max and min values you also sprintf’d), validation, rounding, etc and then sscanf the known-valid string for the final value.

In effect, you’d be moving toward an intermediate representation that’s (a) portable and (b) convenient. C strings are fine at portability, but not so convenient. If you can use external libraries, there are several that are convenient, but whose portability should be confirmed.

For example (which omits rounding):

#include <stdio.h> #include <math.h> #include <limits.h> #include <string.h> int convert(double inVal) { // basic range check - does anybody have an integer format with more than 300 bits? if (fabs(inVal) > 1.0E100) { printf("well out of range"); return 1; } // load string buffer with input char buf[110]; sprintf(buf, "%0105.0f", inVal); // do range check on strings if (inVal < 0) { char minVal[110]; sprintf(minVal, "%0105d", INT_MIN); if (strcmp(buf, minVal) > 0) { printf("too small input: %f\n", inVal); return -1; // needs better error signify } } else { char maxVal[110]; sprintf(maxVal, "%0105d", INT_MAX); if (strcmp(maxVal, buf) < 0) { printf("too large input: %f\n", inVal); return -1; // needs better error signify } } // do final conversion int result; sscanf(buf, "%d", &result); printf("input: %f result: %d\n", inVal, result); // diagnostic return result; } int main() { // test values convert( 0.); convert( -123.5); convert( 123.5); convert( ((double)INT_MIN)-1); convert( ((double)INT_MIN)); convert( ((double)INT_MIN)+1); convert( 2.0*((double)INT_MIN)); convert( ((double)INT_MIN)/2); convert( ((double)INT_MAX)-1); convert( ((double)INT_MAX)); convert( ((double)INT_MAX)+1); convert( 2.0*((double)INT_MAX)); convert( ((double)INT_MAX)/2); return 0; } 

Which produces the expected conversions (see test cases at end above):

% gcc test.c ; ./a.out input: 0.000000 result: 0 input: -123.500000 result: -124 input: 123.500000 result: 124 too small input: -2147483649.000000 input: -2147483648.000000 result: -2147483648 input: -2147483647.000000 result: -2147483647 too small input: -4294967296.000000 input: -1073741824.000000 result: -1073741824 input: 2147483646.000000 result: 2147483646 input: 2147483647.000000 result: 2147483647 too large input: 2147483648.000000 too large input: 4294967294.000000 input: 1073741823.500000 result: 1073741824 

Comments

1

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

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).?

int double_to_int(double val, int *err) 

Detail: (int)val truncates the fractional portion, so the range of convertible val using (int)val is mathematically:
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999... or
INT_MIN - 1 < val < INT_MAX + 1.


Yes a cross-platform way, by using exact floating-point math and constants, code can test for conversion success.

2.0*(INT_MAX/2+1) is certainly exactly converted to a FP constant.

val - INT_MIN > -1.0 is akin to val > INT_MIN - 1.0 but does not suffer imprecision (with the common 2's complement machines) possible with INT_MIN - 1.0. Recall that the integer type may have greater precision than double. Consider a 64-bit int and INT_MIN - 1.0 not exactly representable as a double.

Code does not use (double)INT_MAX which also may be imprecise.


To copy myself:

#include <limits.h> #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 val, int *err) { if (val < DBL_INT_MAXP1) { #if -INT_MAX == INT_MIN // rare non-2's complement machine if (val > DBL_INT_MINM1) { *err = OK; return (int) val; } #else if (val - INT_MIN > -1.0) { *err = OK; return (int) val; } #endif // Underflow *err = ERR_MINUS_INF; return INT_MIN; } if (x > 0) { // Overflow *err = ERR_PLUS_INF; return INT_MAX; } // NaN; *err = ERR_NAN; return 0; } 

Corner weakness: FLT == 10 and the integer type > 34 bits.

Comments

0

The underlying problem is to find min_double_to_int and max_double_to_int, the smallest and largest double, respectively, that can be converted to an int.

The portable conversion function itself can be written in C11 as

int double_to_int(const double value, int *err) { if (!isfinite(value)) { if (isnan(value)) { if (err) *err = ERR_NAN; return 0; } else if (signbit(value)) { if (err) *err = ERR_NEG_INF; return INT_MIN; } else { if (err) *err = ERR_POS_INF; return INT_MAX; } } if (value < min_double_to_int) { if (err) *err = ERR_TOOSMALL; return INT_MIN; } else if (value > max_double_to_int) { if (err) *err = ERR_TOOLARGE; return INT_MAX; } if (err) *err = 0; return (int)value; } 

Before the above function is first used, we need to assign min_double_to_int and max_double_to_int.

EDITED on 2018-07-03: Rewritten approach.

We can use a simple function to find the smallest power of ten that is at least as large as INT_MAX/INT_MIN in magnitude. If those are smaller than DBL_MAX_10_EXP, the range of double is greater than the range of int, and we can cast INT_MAX and INT_MIN to double.

Otherwise, we construct a string containing the decimal representation of INT_MAX/INT_MIN, and use strtod() to convert them to double. If this operation overflows, it means the range of double is smaller than the range of int, and we can use DBL_MAX/-DBL_MAX as max_double_to_int and min_double_to_int, respectively.

When we have INT_MAX as a double, we can use a loop to increment that value using nextafter(value, HUGE_VAL). The largest value that is finite, and rounded down using floor() still yields the same double value, is max_double_to_int.

Similarly, when we have INT_MIN as a double, we can use a loop to decrement that value using nextafter(value, -HUGE_VAL). The largest value in magnitude that is still finite, and rounds up (ceil()) to the same double, is min_double_to_int.

Here is an example program to illustrate this:

#include <stdlib.h> #include <limits.h> #include <string.h> #include <float.h> #include <stdio.h> #include <errno.h> #include <math.h> static double max_double_to_int = -1.0; static double min_double_to_int = +1.0; #define ERR_OK 0 #define ERR_NEG_INF -1 #define ERR_POS_INF -2 #define ERR_NAN -3 #define ERR_NEG_OVER 1 #define ERR_POS_OVER 2 int double_to_int(const double value, int *err) { if (!isfinite(value)) { if (isnan(value)) { if (err) *err = ERR_NAN; return 0; } else if (signbit(value)) { if (err) *err = ERR_NEG_INF; return INT_MIN; } else { if (err) *err = ERR_POS_INF; return INT_MAX; } } if (value < min_double_to_int) { if (err) *err = ERR_NEG_OVER; return INT_MIN; } else if (value > max_double_to_int) { if (err) *err = ERR_POS_OVER; return INT_MAX; } if (err) *err = ERR_OK; return (int)value; } static inline double find_double_max(const double target) { double next = target; double curr; do { curr = next; next = nextafter(next, HUGE_VAL); } while (isfinite(next) && floor(next) == target); return curr; } static inline double find_double_min(const double target) { double next = target; double curr; do { curr = next; next = nextafter(next, -HUGE_VAL); } while (isfinite(next) && ceil(next) == target); return curr; } static inline int ceil_log10_abs(int value) { int result = 1; while (value < -9 || value > 9) { result++; value /= 10; } return result; } static char *int_string(const int value) { char *buf; size_t max = ceil_log10_abs(value) + 4; int len; while (1) { buf = malloc(max); if (!buf) return NULL; len = snprintf(buf, max, "%d", value); if (len < 1) { free(buf); return NULL; } if ((size_t)len < max) return buf; free(buf); max = (size_t)len + 2; } } static int int_to_double(double *to, const int ivalue) { char *ival, *iend; double dval; ival = int_string(ivalue); if (!ival) return -1; iend = ival; errno = 0; dval = strtod(ival, &iend); if (errno == ERANGE) { if (*iend != '\0' || dval != 0.0) { /* Overflow */ free(ival); return +1; } } else if (errno != 0) { /* Unknown error, not overflow */ free(ival); return -1; } else if (*iend != '\0') { /* Overflow */ free(ival); return +1; } free(ival); /* Paranoid overflow check. */ if (!isfinite(dval)) return +1; if (to) *to = dval; return 0; } int init_double_to_int(void) { double target; if (DBL_MAX_10_EXP > ceil_log10_abs(INT_MAX)) target = INT_MAX; else { switch (int_to_double(&target, INT_MAX)) { case 0: break; case 1: target = DBL_MAX; break; default: return -1; } } max_double_to_int = find_double_max(target); if (DBL_MAX_10_EXP > ceil_log10_abs(INT_MIN)) target = INT_MIN; else { switch (int_to_double(&target, INT_MIN)) { case 0: break; case 1: target = -DBL_MAX; break; default: return -1; } } min_double_to_int = find_double_min(target); return 0; } int main(void) { int i, val, err; double temp; if (init_double_to_int()) { fprintf(stderr, "init_double_to_int() failed.\n"); return EXIT_FAILURE; } printf("(int)max_double_to_int = %d\n", (int)max_double_to_int); printf("(int)min_double_to_int = %d\n", (int)min_double_to_int); printf("max_double_to_int = %.16f = %a\n", max_double_to_int, max_double_to_int); printf("min_double_to_int = %.16f = %a\n", min_double_to_int, min_double_to_int); temp = nextafter(max_double_to_int, 0.0); for (i = -1; i <= 1; i++) { val = double_to_int(temp, &err); printf("(int)(max_double_to_int %+d ULP)", i); switch (err) { case ERR_OK: printf(" -> %d\n", val); break; case ERR_POS_OVER: printf(" -> overflow\n"); break; case ERR_POS_INF: printf(" -> infinity\n"); break; default: printf(" -> BUG\n"); } temp = nextafter(temp, HUGE_VAL); } temp = nextafter(min_double_to_int, 0.0); for (i = 1; i >= -1; i--) { val = double_to_int(temp, &err); printf("(int)(min_double_to_int %+d ULP)", i); switch (err) { case ERR_OK: printf(" -> %d\n", val); break; case ERR_NEG_OVER: printf(" -> overflow\n"); break; case ERR_NEG_INF: printf(" -> infinity\n"); break; default: printf(" -> BUG\n"); } temp = nextafter(temp, -HUGE_VAL); } return EXIT_SUCCESS; } 

Comments

0

Perhaps this might work:

#define BYTES_TO_BITS(x) (x*8) void numToIntnt(double num, int *output) { const int upperLimit = ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1))-1; const int lowerLimit = (-1)*ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1)); /* * or a faster approach if the rounding is acceptable: * const int upperLimit = ~(1<<(BYTES_TO_BITS(sizeof(int))-1)); * const int lowerLimit = (1<<(BYTES_TO_BITS(sizeof(int))-1)); */ if(num > upperLimit) { /* report invalid conversion */ } else if (num < lowerLimit) { /* report invalid conversion */ } else { *output = (int)num; } } 

9 Comments

How would that return a value of zero?
@AndrewHenle IMO the goal is to achieve a proper conversion, instead of an error log, which I have tried to simplify it (actually both errors are reporting the same value, in a variable in which any value is expected). Anyway, I have edited the code in order to make it clearer.
The calculation of upperLimit attempts to calculate 2^width-1, where width is the number of bits in an int. Even if some of those bits are padding bits, so they do not contribute to the available values, subtracting 1 is a problem. C does not specify what happens if the result is not exactly representable in floating-point. It might round up or down. Then you do not know whether you should use val < upperLimit or val <= upperLimit.
Testing val > upperLimit will report false for a NaN, as will the other comparison, so this code will fall through to the *err = (int) val case, which we do not want. (Why is it called “err”? That suggests an error, but this is for returning the correct value, is it not?) These tests should be structured so that if the value is in range, then it is converted, else an error is reported. Then NaNs flow to the error path. Or NaNs could be tested for separately.
This code assumes the minimum integer value is the negative of a power of two, but the C standard does not require that.
|
0

Yes. (nan/inf handling omitted for brevity)

int convert(double x) { if (x == INT_MAX) { return INT_MAX; } else if (x > INT_MAX) { err = ERR_OUT_OF_RANGE; return INT_MAX; } else if (x == INT_MIN) { return INT_MIN; } else if (x < INT_MIN) err = ERR_OUT_OF_RANGE; return INT_MIN; } else { return x; } } 

Explanation.

The edge cases, as explained in one of the linked answers, are when INT_MAX is not representable as double exactly, and is rounded up when converted to double, and a symmetric case one with INT_MIN. That's the case when if (x > INT_MAX) fails. That is, the comparison returns false, but we still cannot convert x to int directly.

What the linked answer fails to recognise is that there's only one double number that fails the test, namely (double)INT_MAX, and we can easily catch this case by checking for x == INT_MAX explicitly.

Edit As noted in the comments, this may fail if INT_MAX or INT_MIN is outside of the range of double. While extremely unlikely, this is not precluded by the standard. In such an implementation, the conversion is just (int)x. It should be easier to detect such an implementation at configuration time than at run time. If the latter is absolutely needed, one can perform this operation once:

static int need_simple_conversion = 0; char* str = malloc(sizeof(int)*CHAR_BIT+1); sprintf (str, "%d", INT_MAX); errno = 0; if (strtod(s, NULL) == HUGE_VAL && errno == ERANGE) { // INT_MAX overflows double => double can never overflow int need_simple_conversion = 1; } 

Then

if (need_simple_conversion) return x; else { // as above 

For the paranoid among us, do this with INT_MIN too and perform the check separately for positive and negative doubles.

13 Comments

Hum. One issue here is that INT_MAX could be outside the range of a double. My personal take on that is that the C standard is defective on that quixotic possibility.
@Bathsheba you'd need about 128 bit integers for that, and double that is actually IEEE single-precision float or smaller. This combination, while theoretically possible, is not likely to occur in this here Universe before its thermal death.
@Bathsheba one could check for this with DBL_MAX_EXP > sizeof(int)*CHAR_BITS-1 or something.
Will that be by fire or ice? (Higg's Boson dependent?) On the conversion question, the last sequential whole number exactly represented by a double value is 9,007,199,254,740,992 which is orders of magnitude beyond INT_MAX, so how is INT_MAX not representable as a double in the test x > INT_MAX? All integer values before that are exactly represented by a double.
@DavidC.Rankin The C standard doesn't mandate any of these numbers. There's no upper limit on INT_MAX. OTOH double is allowed to be as small as float.
|
-1

As far as I can tell, the basic problem distills to: is double->int->double an identity for the values of INT_MAX and INT_MIN. Interestingly, C has a way of expressing this:

int isok(int val) { double dv = val; int iv = dv; return val == iv; } 

From this, a much condensed form of the above answers can work, as you can use this to determine whether INT_MAX,INT_MIN are reasonably comparable, thus:

if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) { // do your weirdo float stuff here... } 

but, of course, relying upon C’s rigorous type conversion system gives the compiler free license to reformat your disk, so maybe stuff it through printf/scanf instead.

1 Comment

In the case where double dv = val; rounds to a number of greater magnitude, int iv = dv; is undefined behavior. An example would be INT_MAX for 64-bit 2's-complement int and IEEE 754 double-precision double.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.