Как правильно избежать SIGFPE и переполнения при арифметических операциях

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

Задача:

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

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

Случаи, которые я нашел:

  1. Переполнение на +, -, *, /, pow, root

    Операции в основном прямые (a а также b являются Long):

    • a + b: если LONG_MAX — b> a, то есть переполнение. (недостаточно. a или же b могут быть негативы)
    • a-b: если LONG_MAX — a> -b, то есть переполнение. (Там же)
    • a * b: если LONG_MAX / b> a, то есть переполнение. (если b! = 0)
    • A / B: может бросить SIGFPE, если << б или переполнение, если б << 0
    • pow (a, b): если (pow (LONG_MAX, 1.0 / b)> a, то есть переполнение.
    • pow (a, 1.0 / b): аналогично a / b
  2. Переполнение абс (х), когда х = LONG_MIN (или эквивалентный)

    Это смешно. Каждый тип со знаком имеет диапазон [-x-1, x] возможных значений. abs (-x-1) = x + 1 = -x-1 из-за переполнения. Это означает, что есть случай, когда abs (x) < 0

  3. SIGFPE с большими числами, деленными на -1

    Найдено при применении числитель / GCD (числитель, знаменатель). Иногда gcd возвращает -1, и я получаю исключение с плавающей запятой.

Простые исправления:

  1. На некоторых операциях легко проверить переполнение. Если это так, я всегда могу привести к удвоению (с риском потери точности по большим целым числам). Идея состоит в том, чтобы найти лучшее решение без кастинга.

    В арифметике дробей иногда я могу выполнить дополнительную проверку на упрощения: чтобы решить a / b * c / d (сопряжения), я могу сначала привести к сопряжениям a / d и c / b.

  2. Я всегда могу сделать каскад, если спрашивает, если a или же b являются <0 или> 0. Не самая красивая. Помимо этого ужасного выбора, Я могу создать функцию neg (), которая позволит избежать этого переполнения
    T neg(T x){if (x > 0) return -x; else return x;},
    
  3. Я могу взять abs (x) из gcd и любой подобной ситуации (где угодно x> LONG_MIN)

Я не уверен, что 2. и 3. являются лучшими решениями, но кажется достаточно хорошим. Я публикую их здесь, так что, возможно, у кого-нибудь есть лучший ответ.

Уродливые исправления

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

Пример:

Fraction Fraction::operator+(Fraction f){
double lcm = max(den,f.den);
lcm /= gcd(den, f.den);
lcm *= min(den,f.den);

// a/c + b/d = [a*(lcm/d) + b*(lcm/c)] / lcm    //use to create normal fractions

// a/c + b/d = [a/lcm * (lcm/c)] + [b/lcm * (lcm/d)]    //use to create fractions through double

double p = (double)num;
p *= lcm / (double)den;
double q = (double)f.num;
q *= lcm / (double)f.den;

if(lcm >= LONG_MAX || (p + q) >= LONG_MAX || (p + q) <= LONG_MIN){
//cerr << "Aproximating " << num << "/" << den << " + " << f.num << "/" << f.den << endl;
p = (double)num / lcm;
p *= lcm / (double)den;
q = (double)f.num / lcm;
q *= lcm / (double)f.den;
return Fraction(p + q);
}
else
return normal(p + q, (long)lcm);
}

Каков наилучший способ избежать переполнения этих арифметических операций?


Редактировать: На этом сайте есть несколько вопросов, которые очень похожи, но они не совпадают (обнаружение вместо того, чтобы избежать, без знака, а не со знаком, SIGFPE в конкретных ситуациях, не связанных с).

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

  • Обнаружение переполнения в неподписанном добавлении (не мой случай, я работаю с подписанным):
uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < x; // Alternatively "value < y" should also work

Другие ответы слишком общие, и мне интересно, есть ли какие-либо ответы, более конкретные для рассматриваемых мной случаев.

0

Решение

Вы должны различать плавающая запятая операции и интегральные операции.

Что касается последнего, операции по unsigned типы обычно не переполняются, за исключением деления на ноль, что по определению IIRC является неопределенным поведением. Это тесно связано с тем, что стандарт C (++) предписывает двоичное представление для чисел без знака, что фактически делает их кольцом.

Напротив, стандарт C (++) допускает несколько реализаций signed числа (знак + величина, 1 дополнение или, наиболее широко используемый, 2 дополнение). Таким образом, переполнение со знаком определяется как неопределенное поведение, возможно, чтобы дать разработчикам компилятора больше свободы для генерации эффективного кода для своих целевых машин. Это и есть причина вашего беспокойства abs(): По крайней мере, в представлении дополнения 2 нет положительного числа, которое равный по величине до наибольшего отрицательного числа по величине. Ссылаться на Правила CERT для разработки.

На стороне с плавающей точкой SIGFPE исторически был придуман для сигнализации исключения с плавающей точкой. Однако, учитывая многообразие реализаций арифметических единиц в процессорах в настоящее время, SIGFPE следует рассматривать общий сигнал, сообщающий об арифметических ошибках. Например, справочное руководство glibc дает список возможных причин, в том числе интеграл деление на ноль.

Стоит отметить, что операции с плавающей запятой согласно Стандарт ANSI / IEEE 754, Полагаю, что это чаще всего используется сегодня, я думаю, они специально разработаны для защиты от ошибок. Это означает, что, например, когда дополнение переполняется, оно дает результат бесконечности и обычно устанавливает флаг, который вы можете проверить позже. Вполне допустимо использовать это бесконечное значение в дальнейших вычислениях, поскольку операции с плавающей запятой были определены для аффинной арифметики. Когда-то это должно было позволить продолжительным вычислениям (на медленных машинах) продолжаться даже с промежуточными переполнениями и т. Д. Обратите внимание, что некоторые операции запрещены даже в аффинной арифметике, например, деление бесконечности на бесконечность или вычитание бесконечности на бесконечность.

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

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]