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

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
Volodymyr Boiko
  • 1,533
  • 15
  • 29
  • "Please read before marking as duplicate." should go on comment section – Stargateur Jun 29 '18 at 15:26
  • 1
    I wonder if `frexp` is any help. – Steve Summit Jun 29 '18 at 15:30
  • 1
    I really feel like your question is answer by [the answer](https://stackoverflow.com/questions/8905246/how-to-check-if-float-can-be-exactly-represented-as-an-integer/17822304#17822304) you linked in your question, thus make your question a duplicate. – Stargateur Jun 29 '18 at 15:33
  • You really should explain why the answer shown by Stargateur does not answer your question. – Serge Ballesta Jun 29 '18 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. – Bathsheba Jun 29 '18 at 15:35
  • Should `if val < MAX_INT:` be `if val < MIN_INT:`? – Barmar Jun 29 '18 at 15:37
  • @Barmar: Probably but it doesn't matter really: that method is not portable. – Bathsheba Jun 29 '18 at 15:37
  • @Bathsheba "fail over to a particular implementation" => "even not considering target platforms to support IEEE-75", but so your answer must be correct, it's impossible. Finally, wanting to convert a floating number to an integer is already a problem itself. – Stargateur Jun 29 '18 at 15:40
  • @Stargateur; Absolutely! – Bathsheba Jun 29 '18 at 15:41
  • The [question](https://stackoverflow.com/questions/2544394/c-floating-point-to-integer-type-conversions/2545218) you point to (but call an answer—your link is to the question, not an answer) does not contain the string “digits10”, so I do not see why the lack of a digits10 would preclude using that answer. (Other C++ versus C issues might, but you seem to have mistaken something.) – Eric Postpischil Jun 29 '18 at 15:52
  • Why am I the only upvoter? I don't think this is obvious. Seems like a "go-via-the-string-route" solution is emerging. – Bathsheba Jun 29 '18 at 15:53
  • @EricPostpischil, yes, that's a mistake (also misunderstood meaning of digits) - though in this case the code in answer just wrong. – Volodymyr Boiko Jun 29 '18 at 15:56
  • In the question you link to, you could take the `uint64_t double_to_uint64 (double x)` and modify it for signed (which is easy: check the sign, and use the right arguments to ldexp) – M.M Jul 02 '18 at 07:08
  • @M.M, 1. might be relevant for intN_t but not for int. 2. Nothing prevents DBL_MANT_DIG to be less then 32 - 1. – Volodymyr Boiko Jul 02 '18 at 11:24
  • Can someone explain why standard C `lrint()` can't be used? – Lundin Jul 03 '18 at 07:43
  • @Lundin The lrint and llrint functions round their argument to the nearest integer value, rounding according to the current rounding direction. If the rounded value is outside the range of the return type, the numeric result is unspecified. A range error *may* occur if the magnitude of x is too large. – Volodymyr Boiko Jul 03 '18 at 08:03
  • @Bathsheba: I have completely revised my answer. – Eric Postpischil Jul 05 '18 at 03:18

7 Answers7

3

[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));
}
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • @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, U*type*_MAX+1).” – Eric Postpischil Jul 03 '18 at 01:01
  • 1
    `limits.h` defines `INT_MAX` and `INT_MIN`. – Bob Jarvis - Слава Україні Jul 03 '18 at 01:01
  • @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. – Eric Postpischil Jul 03 '18 at 01:03
  • @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()`. – Nominal Animal Jul 03 '18 at 06:40
  • 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? – Bob Jacobsen Jul 04 '18 at 17:45
  • @BobJacobsen As far as I can see, the question mentions *"cross-platform way (basing only on C standard, even not considering target platforms to support IEEE-754)"*. I doubt the OP is talking about C89, usually the *latest* standard (C11, now) is considered, but you can ask them for clarifications. – Bob__ Jul 04 '18 at 18:29
  • Magic number `0x1p31-1` in the test depends on 32-bit `int`. With wide `int`, `(double)INT_MIN-1` is also problematic. Perhaps a re-write of calls to `Test()` to show when the error value changes? I also tried code, with adjustments for `long long`, and saw no troubles, – chux - Reinstate Monica Jul 12 '18 at 18:09
  • @chux: Yes, the tests are little more than demonstrative, as I did not put any effort into covering a variety of cases that could occur in different C implementations. – Eric Postpischil Jul 13 '18 at 02:16
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
Bob Jacobsen
  • 1,150
  • 6
  • 9
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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
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;
    }
}                                                                                                                          
Jose
  • 3,306
  • 1
  • 17
  • 22
  • How would that return a value of zero? – Andrew Henle Jul 02 '18 at 10:41
  • @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. – Jose Jul 02 '18 at 11:02
  • 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`. – Eric Postpischil Jul 03 '18 at 01:10
  • 1
    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. – Eric Postpischil Jul 03 '18 at 01:12
  • This code assumes the minimum integer value is the negative of a power of two, but the C standard does not require that. – Eric Postpischil Jul 03 '18 at 01:13
  • @EricPostpischil Thank you for your comments, very appreciated (I have edited the variables for readability). Regarding rounding, the problem is even worse from the moment we are using floating-point numbers, I mean, the `double` variable and the hard-code number may be different, so the starting point may be already wrong. – Jose Jul 03 '18 at 07:56
  • and this assumes that `CHAR_BIT == 1` which may be not the case – phuclv May 17 '19 at 15:15
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;
}
Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • 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 Jul 05 '18 at 06:19
  • @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. – n. m. could be an AI Jul 05 '18 at 06:39
  • @Bathsheba one could check for this with `DBL_MAX_EXP > sizeof(int)*CHAR_BITS-1` or something. – n. m. could be an AI Jul 05 '18 at 06:45
  • 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`. – David C. Rankin Jul 05 '18 at 06:46
  • int it could be also 64. – Volodymyr Boiko Jul 05 '18 at 06:51
  • 1
    @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`. – n. m. could be an AI Jul 05 '18 at 06:54
  • OK, I get it, the issue is what the standard mandates -- thus your thermal end comment -- which I agree with. – David C. Rankin Jul 05 '18 at 06:56
  • @DavidC.Rankin my proposed method should also work for other types, e.g. `long long` and `float`, where the max integer value is not exactly representable as a floating point type in most implementations. – n. m. could be an AI Jul 05 '18 at 06:58
  • @n.m. Regarding your first comment, that has a Bill Gates 640k ring to it ;-) With a particularly hostile mantissa / exponent ratio it's in touching distance of an 128 bit `int`. So I *still* think it's an impossible task in portable C. (But would very much like to be proven wrong by counter-example). – Bathsheba Jul 05 '18 at 07:00
  • @Bathsheba when we move to 128-bit ints we'll likely also move to 128-bit or larger doubles, so this is still unlikely to happen in a normal implementation. A *hostile* implementation can be detected like I propose above. In such implementation the conversion is just `(int)x`. This is probably not needed for *practical* portability, which is why I can't be bothered to figure out the exact condition. – n. m. could be an AI Jul 05 '18 at 07:11
  • @n.m. Of course you're absolutely correct. But, it's still not possible to do it *portably*, I think. – Bathsheba Jul 05 '18 at 07:13
  • @n.m one of my dreams is to implement DeathStation-9000 (and conquer the world with it). – Volodymyr Boiko Jul 05 '18 at 07:19
  • the C standard mandated `FLT_DIG >= 6` and `DBL_DIG >= 10`, so `double` can't be IEEE-754 single precision – phuclv May 17 '19 at 15:22
-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.

mevets
  • 10,070
  • 1
  • 21
  • 33
  • 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`. – Pascal Cuoq Jul 04 '18 at 06:38