Обработка переполнения при приведении удвоений к целым числам в C

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

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


person Community    schedule 08.02.2009    source источник


Ответы (9)


При преобразовании чисел с плавающей точкой в ​​целые числа переполнение вызывает неопределенное поведение. Из спецификации 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. Переносимая, безопасная, но немного неточная версия для ints:

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

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

person nwellnhof    schedule 24.05.2015
comment
Я только что провел небольшое эмпирическое тестирование, и этот ответ кажется правильным (опять же, предполагая два дополнительных целых числа; если вы не можете предположить этого, возможно, Boost или SafeInt - единственный разумный способ пойти). Вы должны проголосовать за этот ответ и проголосовать против неправильного ответа, который поддерживает my_double ›INT_MAX || my_double ‹INT_MIN; это на самом деле неверно. - person bhaller; 05.02.2016
comment
@bhaller Я только что проверил, и Boost и SafeInt совершают ту же ошибку, о которой я говорю в своем ответе. - person nwellnhof; 06.06.2016
comment
Ой. Вы сообщили им о проблеме? - person bhaller; 09.06.2016
comment
@bhaller Да, здесь и здесь. - person nwellnhof; 09.06.2016

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

person qrdl    schedule 08.02.2009
comment
У меня это не работает для поплавка. Я пытаюсь сделать это float f = INT_MAX; f++; ConvertToInt(f) с проверкой лимита, указанным выше, и он не переполняется. Что по-другому? - person Pittfall; 11.08.2014
comment
@Pittfall: float имеет (все, кроме очень экзотических платформ, используют числа с плавающей запятой IEEE-754) имеет 24 значащие двоичные цифры. Поэтому, когда вы устанавливаете его на INT_MAX, что составляет 2³¹-1 (INT_MAX на 32-битной платформе), последняя цифра будет 128 с. Итак, если вы добавите что-то меньшее, чем 128, результатом будет исходное число, то есть (float)INT_MAX + 1.f == (float)INT_MAX. С double, который имеет более значащие цифры, чем int, он будет работать. - person Jan Hudec; 06.10.2014
comment
INT_MAX и INT_MIN - это способ проверки C. В C ++ используются std::numeric_limits<int>::max() и …::min(). - person Jan Hudec; 06.10.2014
comment
Этот ответ неверен, потому что не гарантируется, что INT_MIN и INT_MAX имеют точное представление с плавающей запятой. Например, для 64-битных целых чисел INT_MAX равно 2 ^ 63-1, а (double)INT_MAX будет округлено до 2 ^ 63, поэтому эта проверка не обнаружит переполнения, если my_double равно 2 ^ 63. Изменение проверки на my_double >= INT_MAX || my_double < INT_MIN должно работать с двумя дополнительными целыми числами, даже если это выглядит неправильно. - person nwellnhof; 24.05.2015
comment
Я согласен, что это неверный ответ. Его следует удалить. - person bhaller; 05.02.2016
comment
Единственная проблема с nextafter заключается в том, что double, равный INT_MIN, будет обнаруживаться как переполнение, хотя его можно преобразовать в int. В остальном это хорошее портативное решение. - person nwellnhof; 06.06.2016

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

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

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

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

person Nils Pipenbrinck    schedule 08.02.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.

person JaredPar    schedule 08.02.2009
comment
Грубый тест привел меня к мысли, что, вероятно, невозможно обнаружить переполнение в C ++ (без серьезных накладных расходов или полного изменения парадигмы, например, обертывания каждого целого числа как объекта). Этот выделенный класс не может обрабатывать SafeInt<size_t> x = std::numeric_limits<size_t>::max() + 100 (не бросает). - person kizzx2; 02.12.2010
comment
ты смеешься...? ваша правая сторона действительно уже переполнилась еще до того, как достигла SafeInt ctor. Вы не можете винить SafeInt в этом - person Ichthyo; 08.01.2011
comment
SafeInt совершает ту же ошибку, о которой я говорю в своем ответе. SafeInt<int64_t> v = pow(2.0, 63.0) не бросает. - person nwellnhof; 06.06.2016

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

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

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

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

INT_MAX всегда является степенью двойки - 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();
  }
}
person chux - Reinstate Monica    schedule 19.07.2016
comment
Вы говорите, что степень двойки, размер MAX_INT+1, безусловно, может быть представлена ​​как double. Вы можете объяснить почему? Каковы ваши предположения? Достаточно ли предположить, что IEEE? - person Kristian Spangsege; 15.10.2017
comment
Хорошо, теперь я вижу, что вы предполагаете, что FLT_RADIX равно 2, а INT_MAX значительно ниже DBL_MAX, и ничего больше, кроме того, что гарантируется языковым стандартом. Это прекрасное и креативное решение. Я люблю это. - person Kristian Spangsege; 15.10.2017
comment
Один комментарий: то, что вы называете переполнением, я считаю, формально называется отрицательным переполнением. Незаполнение - это когда результат обнуляется. Переполнение - это когда, грубо говоря, сказывается величина. - person Kristian Spangsege; 15.10.2017
comment
@KristianSpangsege В C недополнение с FP соответствует comment, а C использует overflow для результатов за пределами диапазона int (+ и -). Говоря языком, я слышал и читал, что underflow используется с чрезмерно отрицательными результатами, и, поскольку OP использовал его таким образом, имело смысл ответить аналогичным образом. отрицательное переполнение - хороший термин, даже если он немного многословен. - person chux - Reinstate Monica; 15.10.2017
comment
Строго говоря, есть еще одно предположение, а именно, что DBL_MANT_DIG <= DBL_MAX_EXP такое, что результат ceil() всегда представим. Типы IEEE удовлетворяют этому условию. Я взял это на странице руководства Linux для ceil(). - person Kristian Spangsege; 15.10.2017

Встречаем тот же вопрос. Такие как:

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

в Linux / window, i = -2147483648. но в AIX 5.3 i = 2147483647.

Если дубль выходит за пределы диапазона interger.

  • Linux / window всегда возвращает INT_MIN.
  • AIX вернет INT_MAX, если double является положительным, вернет INT_MIN, если double является отрицательным.
person Dong Wang    schedule 23.01.2013
comment
Linux определенно не всегда возвращает INT_MIN, и я был бы удивлен, если бы это сделала Windows, потому что это функция самой архитектуры процессора (она не зависит от операционной системы). - person alastair; 04.06.2019

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

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

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

person fhe    schedule 08.02.2009
comment
boost :: numeric_cast совершает ту же ошибку, о которой я говорю в своем ответе. numeric_cast<int64_t>(pow(2.0, 63.0)) не бросает. - person nwellnhof; 06.06.2016

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

person Autodidact    schedule 08.02.2009

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

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

person Jeff Barger    schedule 08.02.2009