Подтвердить что ты не робот

Действительно ли "epsilon" гарантирует что-либо в вычислениях с плавающей запятой?

Чтобы сделать проблему короткой, скажем, я хочу вычислить выражение a / (b - c) на float s.

Чтобы убедиться, что результат имеет смысл, я могу проверить, равны ли b и c:

float EPS = std::numeric_limits<float>::epsilon();
if ((b - c) > EPS || (c - b) > EPS)
{
    return a / (b - c);
}

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

Случай 1:

a = 1.0f;
b = 0.00000003f;
c = 0.00000002f;

Результат: Условие if не выполняется, но выражение создаст правильный результат 100000008 (как для точности поплавков).

Случай 2:

a = 1e33f;
b = 0.000003;
c = 0.000002;

Результат: Условие if выполнено, но выражение не дает значимого результата +1.#INF00.

Я нашел гораздо надежнее проверять результат, а не аргументы:

const float INF = numeric_limits<float>::infinity();
float x = a / (b - c);
if (-INF < x && x < INF)
{
     return x;
}

Но зачем эпсилон тогда и почему все говорят, что эпсилон хорош для использования?

4b9b3361

Ответ 1

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

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

Если вы не читали Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой, это хорошая отправная точка. Более того, если вас интересует точность результата деления в вашем примере, вы должны оценить, насколько неточными b-c были сделаны предыдущие ошибки округления, потому что, действительно, если b-c мал, небольшая абсолютная ошибка соответствует большой абсолютной ошибке результата. Если ваша забота заключается только в том, что разделение не должно переполняться, тогда ваш тест (по результату) является правильным. Нет никаких оснований проверять нулевой делитель с числами с плавающей запятой, вы просто проверяете переполнение результата, который фиксирует оба случая, когда делитель равен нулю и где делитель настолько мал, чтобы сделать результат не представимым с любой точности.

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

Ответ 2

Epsilon используется для определения того, являются ли два числа, подверженные ошибке округления, достаточно близкими, чтобы считаться "равными". Обратите внимание, что лучше протестировать fabs(b/c - 1) < EPS, чем fabs(b-c) < EPS, и даже лучше -— благодаря дизайну IEEE floats — для тестирования abs(*(int*)&b - *(int*)&c) < EPSI (где EPSI - некоторое небольшое целое).

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