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

前端 未结 7 746
借酒劲吻你
借酒劲吻你 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: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 
    #include 
    #include 
    #include 
    
    
    /*  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 
    
    
    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 
    
    
    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));
    }
    

提交回复
热议问题