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

前端 未结 7 748
借酒劲吻你
借酒劲吻你 2020-12-10 01:42

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/I

相关标签:
7条回答
  • 2020-12-10 02:37

    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;
    }
    
    0 讨论(0)
提交回复
热议问题