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;
}