Предотвратить переполнение / переполнение в делении поплавка - PullRequest
0 голосов
/ 02 октября 2018

У меня есть два числа:

FL_64 variable_number;
FL_64 constant_number;

Номер константы всегда одинаков, например:

constant_number=(FL_64)0.0000176019966602325;

Номер переменной дан мне, и мне нужно выполнитьделение:

FL_64 result = variable_number/constant_number;

Какие проверки мне нужно сделать для variable_number , чтобы убедиться, что операция не переполнится / не опустится до ее выполнения?

Редактировать: FL_64 это просто typedef для double, поэтому FL_64 = double.

Ответы [ 5 ]

0 голосов
/ 02 октября 2018

Я попытаюсь ответить на вопрос, который вы задали (вместо этого я попытаюсь ответить на другой вопрос «Как обнаружить переполнение или недопущение», который вы не задали).

Комупредотвратить переполнение и переполнение для деления при разработке программного обеспечения:

  • Определить диапазон числителя и найти значения с наибольшей и наименьшей абсолютной величиной

  • Определите диапазон делителя и найдите значения с наибольшей и наименьшей абсолютной величиной

  • Убедитесь, что максимальное представимое значение типа данных (например, FLT_MAX) разделенона наибольшую абсолютную величину диапазона делителей больше, чем наибольшая абсолютная величина диапазона числителей.

  • Убедитесь, что минимальное представимое значение типа данных (например, FLT_MIN) умноженное на наименьшую абсолютную величину диапазона делителей меньше наименьшей абсолютной величины of диапазон числителей.

Обратите внимание, что последние несколько шагов, возможно, придется повторять для каждого возможного типа данных, пока вы не найдете «лучший» (наименьший) тип данных, который предотвращаетнедостаточный и недостаточный (например, вы можете проверить, удовлетворяет ли float последние 2 шага и найти, что он не удовлетворяет, а затем проверить, удовлетворяет ли double последние 2 шага и найти, что он выполняет).

ЭтоТакже возможно, что вы обнаружите, что ни один тип данных не может предотвратить переполнение и недополнение, и что вам необходимо ограничить диапазон значений, которые можно использовать для числителя или делителя, или переставить формулы (например, измените (c*a)/b на (c/b)*a) или переключиться на другое представление («двойные двойные», рациональные числа, ...).

Также;имейте в виду, что это гарантирует, что (для всех комбинаций значений в ваших диапазонах) переполнение и недопущение будут предотвращены;но не гарантирует, что будет выбран самый маленький тип данных, если есть какая-то связь между величинами числителей и делителей.Для простого примера, если вы делаете что-то вроде b = a*a+1; result = b/a;, где величина числителя зависит от величины делителя, то вы никогда не получите «наибольший числитель с наименьшим делителем» или «наименьший числитель с наибольшим делителем»"случаи и меньший тип данных (который не может обрабатывать случаи, которые не существуют) могут быть подходящими.

Обратите внимание, что вы также можете выполнять проверки перед каждым отдельным делением.Это имеет тенденцию ухудшать производительность (из-за ветвей / проверок), вызывая дублирование кода (например, предоставляя альтернативный код, который использует double для случаев, когда float вызвал бы переполнение или переполнение);и не может работать, когда поддерживаемый самый большой тип недостаточно велик (в итоге вы сталкиваетесь с проблемой } else { // Now what???, которая не может быть решена способом, обеспечивающим работоспособность значений, потому что обычно единственное, что вы можете сделатьэто рассматривать как условие ошибки).

0 голосов
/ 02 октября 2018

Тест на переполнение

Предположим:

  • В реализации C используется арифметика IEEE-754 с округлением до ближайшего числа, связанного с четным.
  • Величина делителя не более 1, а делитель ненулевой.
  • Делитель положительный.

Тест и приведенное ниже доказательство написаны с помощью приведенного вышепредположения для простоты, но общие случаи легко обрабатываются:

  • Если делитель может быть отрицательным, используйте fabs(divisor) вместо divisor при расчете limit, показанном ниже.
  • Если делитель равен нулю, нет необходимости проверять переполнение, поскольку уже известно, что возникает ошибка (деление на ноль).
  • Если величина превышает 1, деление никогда не создаетсяновый переполнение.Переполнение происходит только в том случае, если дивиденд уже равен бесконечности (поэтому проверка будет isinf(candidate)).(При делителе, превышающем 1 по величине, деление может быть недостаточным. В этом ответе не обсуждается тестирование на предмет недостаточного значения в этом случае.)

Примечание к записи: выражения, использующие операторы не в формате кода, напримеркак xy, представляют точные математические выражения без округления с плавающей точкой.Выражения в формате кода, такие как x*y, означают вычисленные результаты с округлением с плавающей запятой.

Чтобы обнаружить переполнение при делении на divisor, мы можем использовать:

FL_64 limit = DBL_MAX * divisor;
if (-limit <= candidate && candidate <= limit)
    // Overflow will not occur.
else
    // Overflow will occur or candidate or divisor is NaN.

Доказательство:

limit будет равно DBL_MAX, умноженному на divisor и округленному до ближайшего представимого значения.Это точно DBL_MAXdivisor • (1 + e ) для некоторой ошибки e , такой, что -2 −53 e ≤ 2 −53 , благодаря свойствам округления до ближайшего плюс тот факт, что никакое представимое значение для divisor не может при умножении на DBL_MAX привести к значению ниже нормального диапазона.(В субнормальном диапазоне относительная погрешность из-за округления может быть больше, чем 2 -53 . Поскольку продукт остается в нормальном диапазоне, этого не происходит.)

Однако e = 2 -53 может произойти только в том случае, если точное математическое значение DBL_MAXdivisor находится точно посередине между двумя представимыми значениями, что требует наличия 54 значащих бит (бит, равный ½ самой низкой позиции 53-битного значимого числа представимых значений, равен 54 th , считая от начального бита).Мы знаем, что значение DBL_MAX равно 1fffffffffffff 16 (53 бита).Умножение на нечетные числа дает 1fffffffffffff 16 (при умножении на 1), 5ffffffffffffd 16 (на 3) и 0x9ffffffffffffb 16 (на 5) и числас более значимыми битами при умножении на большие нечетные числа.Обратите внимание, что 5ffffffffffffd 16 имеет 55 значащих бит.Ни один из них не имеет ровно 54 значащих бита.При умножении на четные числа произведение имеет конечные нули, поэтому число значащих битов такое же, как и при умножении на нечетное число, которое получается в результате деления четного числа на наибольшую степень двух, которая его делит.Следовательно, ни одно произведение DBL_MAX не находится точно посередине между двумя представленными значениями, поэтому ошибка e никогда не бывает точно 2 -53 .Итак −2 53 <<em> e <2 <sup>-53 .

Итак, limit = DBL_MAXdivisor • (1 + e ), где e <2 <sup>-53 .Следовательно, limit / divisor равно DBL_MAX • (1 + e ).Поскольку этот результат меньше ½ ULP от DBL_MAX, он никогда не округляется до бесконечности, поэтому он никогда не переполняется.Таким образом, деление любого candidate, которое меньше или равно limit на divisor, не переполняется.

Теперь мы рассмотрим кандидатовпревышающий limit.Как и в случае с верхней границей, e не может быть равен -2 -53 по той же причине.Тогда наименьшее значение e может быть равно -2 −53 + 2 −105 , поскольку произведение DBL_MAX и divisor имеет не более 106 значимыхбиты, поэтому любое увеличение от средней точки между двумя представленными значениями должно быть как минимум на одну часть в 2 -105 .Затем, если limit < candidate, candidate хотя бы на одну часть в 2 -52 больше limit, поскольку в значении есть 53 бита.Итак DBL_MAXdivisor • (1−2 −53 + 2 −105 ) • (1 + 2 −52 ) <<code>candidate,Тогда candidate / divisor не менее DBL_MAX • (1−2 −53 + 2 −105 ) • (1 + 2 −52 ), что составляет DBL_MAX • (1 + 2 −53 + 2 −157 ).Превышение средней точки между DBL_MAX и тем, что будет следующим представимым значением, если диапазон экспоненты не ограничен, что является основой для критерия округления IEEE-754.Следовательно, он округляется до бесконечности, поэтому возникает переполнение.

Недостаточное значение

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

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

0 голосов
/ 02 октября 2018

Лично я не знаю тип переменной FL_64, исходя из названия, которое, как я полагаю, имеет 64-битное представление, но является ли оно знаковым или беззнаковым?

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

В случае подписи необходимо проверить знак результата:

FL_64 result = variable_number/constant_number;

if ((variable_number > 0 && constant_number > 0) || (variable_number < 0 && constant_number < 0)) {
    if (result < 0) {
        //OVER/UNDER FLOW
        printf("over/under flow");
    } else {
        //NO OVER/UNDER FLOW
        printf("no over/under flow");
    }
} else {
    if (result < 0) {
        //NO OVER/UNDER FLOW
        printf("no over/under flow");
    } else {
        //OVER/UNDER FLOW
        printf("over/under flow");
    }
}

Такжедругие случаи должны быть проверены, например деление на 0. Но, как вы упомянули, constant_number всегда фиксировано и отличается от 0.

РЕДАКТИРОВАТЬ:

Хорошо, так что может быть другой способ проверить переполнениеиспользуя значение DBL_MAX.Имея максимальное повторяемое число на двойном, вы можете умножить его на constant_number и вычислить максимальное значение для variable_number.Из приведенного ниже фрагмента кода видно, что первый случай не вызывает переполнения, а второй - (так как variable_number - это большее число по сравнению с test).Фактически из вывода консоли вы можете видеть, что первое значение result выше второго, даже если оно на самом деле должно быть в два раза больше предыдущего.Так что этот случай - случай переполнения.

#include <stdio.h>
#include <float.h>

typedef double FL_64;

int main() {
    FL_64 constant_number = (FL_64)0.0000176019966602325;
    FL_64 test = DBL_MAX * constant_number;
    FL_64 variable_number = test;
    FL_64 result;

    printf("MAX double value:\n%f\n\n", DBL_MAX);

    printf("Variable Number value:\n%f\n\n", variable_number);
    printf(variable_number > test ? "Overflow case\n\n" : "No overflow\n\n");
    result = variable_number / constant_number;
    printf("Result: %f\n\n", variable_number);

    variable_number *= 2;

    printf("Variable Number value:\n%f\n\n", variable_number);
    printf(variable_number > test ? "Overflow case\n\n" : "No overflow\n\n");
    result = variable_number / constant_number;
    printf("Result:\n%f\n\n", variable_number);

    return 0;
}

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

0 голосов
/ 02 октября 2018

Предполагая, что FL_64 является чем-то вроде double, вы можете получить максимальное значение с именем DBL_MAX из float.h

Таким образом, вы хотите убедиться, что

DBL_MAX >= variable_number/constant_number

или в равной степени

DBL_MAX * constant_number >= variable_number

В коде это может быть что-то вроде

if (constant_number > 0.0 && constant_number < 1.0)
{
    if (DBL_MAX * constant_number >= variable_number)
    {
        // wont overflow
    }
    else
    {
        // will overflow
    }
}
else
{
    // add code for other ranges of constant_number
}

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

0 голосов
/ 02 октября 2018

Я не знаю, какого стандарта придерживается ваш FL_64, но если это что-то вроде IEEE 754 , вы захотите остерегаться

Не число

Может быть специальное значение NaN.В некоторой реализации результат сравнения с чем-либо равен 0, так что если (variable_number == variable_number) == 0, то это то, что происходит.Могут быть макросы и функции для проверки этого в зависимости от реализации, например, в GNU C Library .

Infinity

IEEE 754 также поддерживает бесконечность (и отрицательныйбесконечность).Это может быть результатом переполнения, например.Если variable_number бесконечно, и вы разделите его на constant_number, результат, вероятно, снова будет бесконечным.Как и в случае NaN, реализация обычно предоставляет макросы или функции для проверки этого, в противном случае вы можете попробовать разделить число на что-то и посмотреть, не стало ли оно меньше.

Переполнение

После делениячисло на constant_number сделает его больше, variable_number может переполниться, если оно уже огромно.Проверьте, не слишком ли это велико, чтобы это могло произойти.Но в зависимости от вашей задачи, вероятность того, что она будет такой большой, уже может быть исключена.64-разрядные числа с плавающей запятой в IEEE 754 доходят до 10 ^ 308.Если ваше число переполнено, оно может превратиться в бесконечность.

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