Может ли преобразование из double в int записываться в переносимый C - PullRequest
0 голосов
/ 29 июня 2018

Мне нужно написать такую ​​функцию, как double_to_int(double val, int *err), которая конвертирует double val в integer, когда это возможно; в противном случае сообщите об ошибке (NAN / INFs / OUT_OF_RANGE).

так что реализация псевдокода будет выглядеть так:

if isnan(val):
    err = ERR_NAN
    return 0
if val < MAX_INT:
    err = ERR_MINUS_INF
    return MIN_INT
if ...
return (int)val

Есть как минимум два похожих вопроса по SO: в этом ответе он решен достаточно чисто, хотя это решение C ++ - в C у нас нет переносимых цифр для подписанного int. В этом ответе объясняется, почему мы не можем просто проверить (val > INT_MAX || val < INT_MIN).

Так что единственный возможный чистый способ, который я вижу, - это использование среды с плавающей запятой, но она определена как функция, определяемая реализацией.

Итак, мой вопрос: есть ли способ реализовать функцию double_to_int в кросс-платформенном режиме (основываясь только на стандарте C, даже не принимая во внимание целевые платформы для поддержки IEEE-754).?

Ответы [ 8 ]

0 голосов
/ 12 июля 2018

Может ли преобразование из double в int быть записано в переносимый C (?)

есть ли способ реализовать функцию double_to_int кроссплатформенным способом (основываясь только на стандарте C, даже если не учитывать целевые платформы для поддержки IEEE-754).?

int double_to_int(double val, int *err)

Детализация: (int)val усекает дробную часть, поэтому диапазон конвертируемых val с использованием (int)val математически :
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999... или
INT_MIN - 1 < val < INT_MAX + 1.


Да, кроссплатформенный способ, используя точные математические и константы с плавающей точкой, позволяет проверять код на успешность преобразования.

2.0*(INT_MAX/2+1) определенно точно преобразуется в константу FP.

val - INT_MIN > -1.0 сродни val > INT_MIN - 1.0, но не терпит неточностей (с обычными 2-мя дополнительными машинами), возможными с INT_MIN - 1.0. Напомним, что целочисленный тип может иметь большую точность, чем double. Рассмотрим 64-битные int и INT_MIN - 1.0, которые нельзя точно представить как double.

Код не использует (double)INT_MAX, что также может быть неточным.


Чтобы скопировать себя :

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

Угловая слабость: FLT == 10 и целочисленный тип> 34 бита.

0 голосов
/ 04 июля 2018

Насколько я могу судить, основная проблема сводится к следующему: это double-> int-> double для значений INT_MAX и INT_MIN. Интересно, что у C есть способ выразить это:

int isok(int val) {
   double dv = val;
   int iv = dv;
   return val == iv;
}

Исходя из этого, может работать очень сжатая форма приведенных выше ответов, так как вы можете использовать это, чтобы определить, являются ли INT_MAX, INT_MIN разумно сопоставимыми, таким образом:

if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) {
     // do your weirdo float stuff here...
}

но, конечно, использование строгой системы преобразования типов C дает бесплатную лицензию компилятора для переформатирования вашего диска, так что, возможно, вместо этого запишите его через printf / scanf.

0 голосов
/ 05 июля 2018

Да. (обработка nan / inf опущена для краткости)

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

Пояснение.

Крайние случаи, как объяснено в одном из связанных ответов, это когда INT_MAX не представляется точно как double и округляется при преобразовании в double, и симметричный случай с INT_MIN , Это тот случай, когда if (x > INT_MAX) не удается. То есть сравнение возвращает false, но мы все равно не можем преобразовать x в int напрямую.

Что связанный ответ не может распознать, так это то, что есть только одно двойное число, которое не проходит тест, а именно (double)INT_MAX, и мы можем легко уловить этот случай, явно указав x == INT_MAX.

Редактировать Как отмечено в комментариях, это может не произойти, если INT_MAX или INT_MIN находится за пределами диапазона double. Хотя это крайне маловероятно, стандарт не исключает этого. В такой реализации преобразование составляет всего (int)x. Должно быть легче обнаружить такую ​​реализацию во время конфигурации, чем во время выполнения. Если последнее абсолютно необходимо, можно выполнить эту операцию один раз :

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

Тогда

if (need_simple_conversion)
    return x;
else { // as above

Для параноиков среди нас, сделайте это также с INT_MIN и выполните проверку отдельно для положительных и отрицательных двойных чисел.

0 голосов
/ 03 июля 2018

Основная проблема заключается в том, чтобы найти min_double_to_int и max_double_to_int, самый маленький и самый большой double, соответственно, которые можно преобразовать в int.

Сама портативная функция преобразования может быть записана в C11 как

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

Перед первым использованием вышеуказанной функции нам нужно присвоить min_double_to_int и max_double_to_int.

РЕДАКТИРОВАНИЕ на 2018-07-03: переписанный подход.

Мы можем использовать простую функцию, чтобы найти наименьшую степень десяти, которая по крайней мере равна INT_MAX / INT_MIN по величине. Если они меньше, чем DBL_MAX_10_EXP, диапазон double больше, чем диапазон int, и мы можем разыграть INT_MAX и INT_MIN до double.

В противном случае мы создаем строку, содержащую десятичное представление INT_MAX / INT_MIN, и используем strtod(), чтобы преобразовать их в double. Если эта операция переполнена, это означает, что диапазон double меньше диапазона int, и мы можем использовать DBL_MAX / -DBL_MAX в качестве max_double_to_int и min_double_to_int соответственно.

Когда у нас есть INT_MAX как double, мы можем использовать цикл для увеличения этого значения, используя nextafter(value, HUGE_VAL). Наибольшее значение, которое является конечным и округляется в меньшую сторону с использованием floor(), все еще дает то же значение double, равное max_double_to_int.

Точно так же, когда у нас есть INT_MIN как двойное число, мы можем использовать цикл для уменьшения этого значения, используя nextafter(value, -HUGE_VAL). Наибольшее значение по величине, которое все еще является конечным и округляется (ceil()) до того же double, составляет min_double_to_int.

Вот пример программы, иллюстрирующей это:

#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 голосов
/ 02 июля 2018

Возможно, это может сработать:

#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 голосов
/ 29 июня 2018

Ответ на « Может ли разговор с двойного на int записываться на переносном языке C» явно « да ».

Например, вы можете перебросить плавающее значение в строку, выполнить проверку на основе строки (т. Е. Путем сравнения строки с максимальными и минимальными значениями, которые вы также sprintf'd), проверку, округление и т. Д., А затем выполнить сканирование известного -действительная строка для окончательного значения.

По сути, вы переходите к промежуточному представлению, которое (а) переносимо и (б) удобно. C-струны прекрасно переносимы, но не очень удобны. Если вы можете использовать внешние библиотеки, есть несколько удобных, но переносимость которых должна быть подтверждена.

Например (без округления):

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

, который производит ожидаемые преобразования (см. Контрольные примеры в конце выше):

% 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 голосов
/ 29 июня 2018

[Этот ответ был отредактирован с совершенно новым подходом.]

В этом подходе используется определение форматов с плавающей запятой в стандарте C - в виде числа со знаком - b , умноженного на степень b . Зная количество цифр в значимом (предоставленном DBL_MANT_DIG) и пределе экспоненты (предоставленном DBL_MAX_EXP), мы можем подготовить точные значения double в качестве конечных точек.

Я полагаю, что он будет работать во всех соответствующих реализациях C при условии соблюдения скромных дополнительных требований, изложенных в первоначальном комментарии.

/*  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 голосов
/ 29 июня 2018

(Этот ответ оспаривается, хотя я все еще думаю, что я прав, поэтому, пожалуйста, не высказывайтесь глупо.)

Вы не можете реализовать такую ​​функцию в переносном C.

В этом отношении это скорее похоже на malloc & c.

Мораль этой истории в действительности заключается в том, что смешивание типов в C никогда не бывает хорошей идеей; т.е. писать код таким образом, чтобы преобразование типов не требовалось.

...