Какой безопасный способ конвертировать double в int? - PullRequest
2 голосов
/ 22 марта 2020

Мне дали унаследованный код , где кто-то (-ы) небрежно присвоил double значения int переменным, таким как:

int a = 10;
double b = 20;
a = b;

Теперь, чтобы избавиться от предупреждение

C4244: '=': преобразование из 'double' в 'int', возможная потеря данных

предупреждения, я попытался отредактировать код в восходящем направлении и получить избавиться от ненужных double переменных, но это оказывается слишком грязно!

Я мог бы также использовать приведение:

a = (int) b;

, но на самом деле нет гарантии, что b будет быть в пределах целого числа. Я думал о создании вспомогательной функции:

int safeD2I(double inputVar){
  if ((inputVar < INT_MAX) && (INT_MIN < inputVar)){
    return (int) inputVar;
  } else {
    exit(-1);
  }
}

, но я не уверен, что это лучшая реализация. Мне было интересно, есть ли более канонический способ решения этой проблемы?

что я хочу:

  • в случае, если переменная b находится вне целочисленных границ, Программа немедленно останавливается
  • выводит на терминал сообщение об ошибке, указывающее, что указана c строка и время, когда возникла эта проблема.

Заранее благодарим за поддержку.

Ответы [ 2 ]

5 голосов
/ 22 марта 2020

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

Если вы обнаружите, что вам нужно поймать возможные значения за пределами допустимых значений (которые приводят к неопределенному поведению (!!) при преобразовании в int), ваш код неверен и равен agile. Сравнения, такие как:

double x;
...
if (x < INT_MAX) ...

принудительно INT_MAX для типа double для сравнения. На практике в мире, где double - это двойной IEEE, а int - 32-разрядный, это оказывается безопасным, но это будет небезопасно, например, если вы изменили double на float, так как 32-разрядный INT_MAX не может быть представлен с одинарной точностью float. Значение будет округлено, а затем будет выполнено сравнение после округления.

Теперь, оказывается, у вас также есть ошибка "по одному" (<= INT_MAX, а не < INT_MAX, это то, что в -bounds), а также неверные логики c (|| вместо &&), поэтому округление фактически исправит часть этого. Но не стоит зависеть от этого. Вместо этого вам нужно произвести степень двойки, с которой вы можете сравнивать, так что конвертировать в плавающую точку можно безопасно. Например:

  • if (x < 2.0*(INT_MAX/2+1) && x >= INT_MIN)
  • if (-x > (-INT_MAX-1) && x >= INT_MIN)
  • if (-x > INT_MIN && x >= INT_MIN)

Все они предполагают, что INT_MIN на самом деле степень двойки (дополнение к полному диапазону), которая является разумным предположением реального мира и требуется C2x +. Если вы хотите больше общности, это больше работы.

Наконец, я сначала написал это как комментарий к вашему вопросу, но чем больше я думаю об этом, он действительно принадлежит к ответу: основываясь на том, что ваш C Кажется, что я умел бы очень осторожно делать какие-либо ненужные или "зачистки" изменения в этом коде. Вы, вероятно, сломаете намного больше, чем исправите. Вносите изменения только в том случае, если вы провели анализ, чтобы определить, есть ли активная ошибка, и не реорганизуйте вещи и не изменяйте типы для исправления. Вносите простые прямые исправления.

1 голос
/ 22 марта 2020

Каков безопасный способ конвертировать double в int?

if ((inputVar < INT_MAX) && (INT_MIN < inputVar)){ не подходит для крайних случаев.

Неправильные края, поскольку они больше like , но не совсем как (inputVar < INT_MAX + 1) && (INT_MIN - 1 < inputVar)

Остерегайтесь кода, подобного some_FP < SOME_INT_MAX, так как SOME_INT_MAX может не преобразовать в требуемое значение FP, поскольку целочисленный тип может иметь большую точность, чем FP. Это не обычно проблема с int, double.


Проверьте, находится ли double в диапазоне (INT_MIN-1 .... INT_MAX+1) 1 . Примечание (), а не [].

Если нет, выведите ошибку или обработайте каким-либо определенным способом по вашему выбору.

Предполагая типичное дополнение 2, но не предполагая, что точность double превышает int (более полезно для переноса код в float, long long), пример кода:

// FP version of INT_MAX + 1.0 
// Avoid direct (INT_MAX + 1.0) as that can have precision woes
#define DBL_INTMAX_P1 ((INT_MAX/2 + 1)*2.0)

int X_int_from_double(double x) {
  // Coded to insure NAN fails the if()
  if (!(x - INT_MIN > -1.0 && x < DBL_INTMAX_P1)) {
    errno = ERANGE;
    fprintf(stderr, "Error in %s,  %.*e too large\n", __func__, DBL_DECIMAL_DIG - 1, x);

    exit(EXIT_FAILURE);
    // or additional code to handle conversion in some specified manner
    // Example: assuming "wrap"
    if (!isfinite(x)) {
      if (!isnan(x)) return 0;
      if (x > 0) return INT_MAX;
      else return INT_MIN; 
    }
    modf(x, &x); // drop fraction
    x = fmod(x, DBL_INTMAX_P1*2);
    if (x >= DBL_INTMAX_P1) x -= DBL_INTMAX_P1*2;
    else if (x < -DBL_INTMAX_P1) x += DBL_INTMAX_P1*2;
  }
  return (int) x;
}

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

int X_int_from_double(double x, unsigned);
#define DOUBLE_TO_INT(x) X_int_from_double((x), __LINE__)

1 Пример -2 147 483 648,9999 ... до 2 147 483 647,9999 ...

...