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

前端 未结 7 747
借酒劲吻你
借酒劲吻你 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:12

    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;
        }
    }                                                                                                                          
    
    0 讨论(0)
  • 2020-12-10 02:13

    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
    
    0 讨论(0)
  • 2020-12-10 02:23

    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.

    0 讨论(0)
  • 2020-12-10 02:26

    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.

    0 讨论(0)
  • 2020-12-10 02:28

    [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));
    }
    
    0 讨论(0)
  • 2020-12-10 02:32

    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.

    0 讨论(0)
提交回复
热议问题