Я пытался создать класс Fraction настолько полно, насколько это возможно, чтобы самостоятельно изучать C ++, классы и связанные с ними вещи. Помимо прочего, я хотел обеспечить некоторый уровень «защиты» от исключений и переполнений с плавающей запятой.
Избегайте переполнения и исключений с плавающей запятой в арифметических операциях, встречающихся в обычных операциях, затрачивая меньше времени и памяти. Если избежать невозможно, по крайней мере, обнаружить его.
Кроме того, идея состоит в том, чтобы не приводить к более крупным типам. Это создает кучу проблем (например, не может быть более крупного типа)
Переполнение на +, -, *, /, 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
Переполнение абс (х), когда х = LONG_MIN (или эквивалентный)
Это смешно. Каждый тип со знаком имеет диапазон [-x-1, x] возможных значений. abs (-x-1) = x + 1 = -x-1 из-за переполнения. Это означает, что есть случай, когда abs (x) < 0
Найдено при применении числитель / GCD (числитель, знаменатель). Иногда gcd возвращает -1, и я получаю исключение с плавающей запятой.
В арифметике дробей иногда я могу выполнить дополнительную проверку на упрощения: чтобы решить a / b * c / d (сопряжения), я могу сначала привести к сопряжениям a / d и c / b.
a
или же b
являются <0 или> 0. Не самая красивая. Помимо этого ужасного выбора,T neg(T x){if (x > 0) return -x; else return x;},
Я не уверен, что 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
Обнаружить переполнение в подписанных операциях. Это может быть слишком общим, с большим количеством веток и не обсуждать, как избежать переполнения.
Правила CERT Упоминаемые в ответе, являются хорошей отправной точкой, но опять же обсудим только, как их обнаружить.
Другие ответы слишком общие, и мне интересно, есть ли какие-либо ответы, более конкретные для рассматриваемых мной случаев.
Вы должны различать плавающая запятая операции и интегральные операции.
Что касается последнего, операции по unsigned
типы обычно не переполняются, за исключением деления на ноль, что по определению IIRC является неопределенным поведением. Это тесно связано с тем, что стандарт C (++) предписывает двоичное представление для чисел без знака, что фактически делает их кольцом.
Напротив, стандарт C (++) допускает несколько реализаций signed
числа (знак + величина, 1 дополнение или, наиболее широко используемый, 2 дополнение). Таким образом, переполнение со знаком определяется как неопределенное поведение, возможно, чтобы дать разработчикам компилятора больше свободы для генерации эффективного кода для своих целевых машин. Это и есть причина вашего беспокойства abs()
: По крайней мере, в представлении дополнения 2 нет положительного числа, которое равный по величине до наибольшего отрицательного числа по величине. Ссылаться на Правила CERT для разработки.
На стороне с плавающей точкой SIGFPE
исторически был придуман для сигнализации исключения с плавающей точкой. Однако, учитывая многообразие реализаций арифметических единиц в процессорах в настоящее время, SIGFPE
следует рассматривать общий сигнал, сообщающий об арифметических ошибках. Например, справочное руководство glibc дает список возможных причин, в том числе интеграл деление на ноль.
Стоит отметить, что операции с плавающей запятой согласно Стандарт ANSI / IEEE 754, Полагаю, что это чаще всего используется сегодня, я думаю, они специально разработаны для защиты от ошибок. Это означает, что, например, когда дополнение переполняется, оно дает результат бесконечности и обычно устанавливает флаг, который вы можете проверить позже. Вполне допустимо использовать это бесконечное значение в дальнейших вычислениях, поскольку операции с плавающей запятой были определены для аффинной арифметики. Когда-то это должно было позволить продолжительным вычислениям (на медленных машинах) продолжаться даже с промежуточными переполнениями и т. Д. Обратите внимание, что некоторые операции запрещены даже в аффинной арифметике, например, деление бесконечности на бесконечность или вычитание бесконечности на бесконечность.
Итак, суть в том, что вычисления с плавающей запятой обычно не должны вызывать исключения с плавающей запятой. Все же вы можете иметь так называемый ловушки что вызывает SIGFPE
(или аналогичный механизм), который должен срабатывать всякий раз, когда вышеупомянутые флаги становятся поднятыми.
Других решений пока нет …