Обработка переполнения при приведении удваивается к целым числам в C - PullRequest
25 голосов
/ 08 февраля 2009

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

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

Ответы [ 9 ]

15 голосов
/ 24 мая 2015

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

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

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

// 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 s:

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

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

12 голосов
/ 08 февраля 2009

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

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

РЕДАКТИРОВАТЬ: nextafter() решит проблему, упомянутую nwellnhof

11 голосов
/ 08 февраля 2009

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

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

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

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

4 голосов
/ 08 февраля 2009

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

http://www.codeplex.com/SafeInt

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

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

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

SafeInt теперь работает как в GCC, так и в Visual Studio

3 голосов
/ 20 июля 2016

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

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

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

То же самое относится к INT_MIN на машинах, отличных от 2-х.

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();
  }
}
2 голосов
/ 23 января 2013

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

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

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

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

  • Linux / окно всегда возвращает INT_MIN.
  • AIX вернет INT_MAX, если double положительный, вернет INT_MIN из двойник отрицательный.
2 голосов
/ 08 февраля 2009

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

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

Конечно, это уже не просто C; -)

0 голосов
/ 08 февраля 2009

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

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

0 голосов
/ 08 февраля 2009

Я не уверен в этом, но я думаю, что может быть возможно "включить" исключения с плавающей запятой для under / overflow ... посмотрите на это Работа с исключениями с плавающей точкой в ​​MSVC7 \ 8 так что у вас может быть альтернатива проверкам if / else.

...