Подтвердить что ты не робот

Обработка переполнения, когда литье удваивается до целых чисел в C

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

Является ли это поведение определенным для всех платформ?
Каков наилучший способ обнаружить это под/переполнение? Помещает ли инструкции if для min и max int перед отличным решением?

4b9b3361

Ответ 1

limits.h имеет константы для max и min возможных значений для целочисленных типов данных, вы можете проверить свою двойную переменную до кастования, например

if (my_double > nextafter(INT_MAX, 0) || my_double < nextafter(INT_MIN, 0))
    printf("Overflow!");
else
    my_int = (int)my_double;

EDIT: nextafter() решит проблему, упомянутую nwellnhof

Ответ 2

Чтобы ответить на ваш вопрос: поведение, когда вы выбрасываете плавающие диапазоны, undefined или конкретное исполнение.

Говоря по опыту: я работал над системой MIPS64, которая вообще не выполняла эти ролики. Вместо того, чтобы делать что-то детерминированное, процессор бросает исключение CPU. Обработчик исключений, который должен эмулировать бросок, возвращался без каких-либо результатов.

Я закончил со случайными целыми числами. Угадайте, сколько времени потребовалось, чтобы отследить ошибку в этой причине.:-)

Лучше сделайте проверку диапазона, если вы не уверены, что номер не может выйти из допустимого диапазона.

Ответ 3

При отливке чисел с плавающей точкой в ​​целые числа переполнение вызывает поведение undefined. Из спецификации C99, раздел 6.3.1.4 Реальное плавающее и целое число:

Когда конечное значение реального плавающего типа преобразуется в целочисленный тип, отличный от _Bool, дробная часть отбрасывается (т.е. значение усекается к нулю). Если значение целой части не может быть представлено целым типом, поведение undefined.

Вы должны проверить диапазон вручную, но не использовать код, например:

// DON'T use code like this!
if (my_double > INT_MAX || my_double < INT_MIN)
    printf("Overflow!");

INT_MAX - целочисленная константа, в которой может не иметь точного представления с плавающей запятой. По сравнению с поплавком он может быть округлен до ближайшего более высокого или ближайшего нижнего представляемого значения с плавающей запятой (это определяется реализацией). С 64-битными целыми числами, например, INT_MAX - 2^63 - 1, который обычно округляется до 2^63, поэтому проверка по существу становится my_double > INT_MAX + 1. Это не будет обнаруживать переполнение, если my_double равно 2^63.

Например, с gcc 4.9.1 в Linux, следующая программа

#include <math.h>
#include <stdint.h>
#include <stdio.h>

int main() {
    double  d = pow(2, 63);
    int64_t i = INT64_MAX;
    printf("%f > %lld is %s\n", d, i, d > i ? "true" : "false");
    return 0;
}

печатает

9223372036854775808.000000 > 9223372036854775807 is false

Трудно получить это право, если вы заранее не знаете границ и внутреннего представления целочисленного и двойного типов. Но если вы конвертируете из double в int64_t, например, вы можете использовать константы с плавающей запятой, которые являются точными удвоениями (при условии, что два дополнения и IEEE удваиваются):

if (!(my_double >= -9223372036854775808.0   // -2^63
   && my_double <   9223372036854775808.0)  // 2^63
) {
    // Handle overflow.
}

Конструкция !(A && B) также корректно обрабатывает NaN. Портативная, безопасная, но слегка неточная версия для int:

if (!(my_double > INT_MIN && my_double < INT_MAX)) {
    // Handle overflow.
}

Это заблуждается на стороне осторожности и ложно отклонит значения, равные INT_MIN или INT_MAX. Но для большинства приложений это должно быть хорошо.

Ответ 4

Переносимым способом для С++ является использование класса SafeInt:

http://www.codeplex.com/SafeInt

Реализация позволит нормальное сложение/вычитание/etc на тип числа С++, включая отбрасывание. Он будет генерировать исключение всякий раз, когда и сценарий переполнения обнаружен.

SafeInt<int> s1 = INT_MAX;
SafeInt<int> s2 = 42;
SafeInt<int> s3 = s1 + s2;  // throws

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

SafeInt теперь работает на GCC, а также на Visual Studio

Ответ 5

Каков наилучший способ обнаружить это под/переполнение?

Сравните усеченный double с точными пределами около INT_MIN,INT_MAX.

Трюк состоит в том, чтобы точно преобразовать лимиты на основе INT_MIN,INT_MAX в значения double. A double может не точно представлять INT_MAX, поскольку количество бит в int может превышать точность с плавающей запятой. В этом случае преобразование INT_MAX в double страдает от округления. Число после INT_MAX является степенью-2 и определенно представляется как a double. 2.0*(INT_MAX/2 + 1) генерирует целое число больше, чем INT_MAX.

То же самое относится к INT_MIN для машин с не-2s-дополнением.

INT_MAX всегда имеет силу-2 - 1.
INT_MIN всегда:
  -INT_MAX (не 2 дополнения) или
  -INT_MAX-1 (2 дополнения)

int double_to_int(double x) {
  x = trunc(x);
  if (x >= 2.0*(INT_MAX/2 + 1)) Handle_Overflow();
  #if -INT_MAX == INT_MIN
  if (x <= 2.0*(INT_MIN/2 - 1)) Handle_Underflow();
  #else
  if (x < INT_MIN) Handle_Underflow();
  #endif
  return (int) x;
}

Чтобы обнаружить NaN и не использовать trunc()

#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 x) {
  if (x < DBL_INT_MAXP1) {
    #if -INT_MAX == INT_MIN
    if (x > DBL_INT_MINM1) {
      return (int) x;
    }
    #else
    if (ceil(x) >= INT_MIN) {
      return (int) x;
    }
    #endif 
    Handle_Underflow();
  } else if (x > 0) {
    Handle_Overflow();
  } else {
    Handle_NaN();
  }
}

Ответ 6

Другой вариант - использовать boost:: numeric_cast, который позволяет произвольно преобразовывать числовые типы. Он обнаруживает потерю диапазона при преобразовании числового типа и генерирует исключение, если диапазон не может быть сохранен.

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

Конечно, это не просто C больше; -)

Ответ 7

Мы встречаем тот же вопрос. например:

double d = 9223372036854775807L;
int i = (int)d;

в Linux/окне, я = -2147483648. но в AIX 5.3 я = 2147483647.

Если double находится вне диапазона интергеров.

  • Linux/window всегда возвращает INT_MIN.
  • AIX вернет INT_MAX, если double является postive, вернет INT_MIN из double - незабудка.

Ответ 9

Я не могу точно сказать, определен ли он для всех платформ, но это в значительной степени то, что произошло на каждой используемой нами платформе. Кроме того, по моему опыту, он катится. То есть, если значение double равно INT_MAX + 2, тогда, когда результат литья заканчивается INT_MIN + 2.

Что касается лучшего способа справиться с этим, я действительно не уверен. Я сам столкнулся с проблемой, и мне еще предстоит найти элегантный способ справиться с этим. Я уверен, что кто-то ответит, что может помочь нам там.