Действительно ли «эпсилон» гарантирует что-либо в вычислениях с плавающей точкой? - PullRequest
17 голосов
/ 28 апреля 2010

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

Чтобы убедиться, что результат имеет смысл, я могу проверить, равны ли 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;
}

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

Ответы [ 2 ]

19 голосов
/ 28 апреля 2010

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

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

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

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

2 голосов
/ 28 апреля 2010

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

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

...