c ++ сравнивает два значения с плавающей точкой - PullRequest
5 голосов
/ 21 февраля 2011

Мне интересно, в чем разница для сравнения двух двойных между этими двумя способами:

double a1 = ...;
double a2 = ....;
  1. fabs (a1-a2)
  2. (fabs (a1-a2) / a2)

Есть ли предпочтительный способ сделать это?

спасибо

Ответы [ 5 ]

13 голосов
/ 21 февраля 2011

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

3 голосов
/ 07 февраля 2016

Лично я использую std::nextafter для сравнения двух double. При этом используется наименьшее значение epsilon для значения (или значение factor наименьшего значения epsilon).

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}
2 голосов
/ 21 февраля 2016

Эпсилон изменяется в зависимости от значения в двойном диапазоне.Если вы хотите использовать свои собственные и исправленные эпсилон, я рекомендую выбрать эпсилон для 1.

fabs(a1-a2) < epsilon

Это не идеально.Если a1 и a2 являются результатом операций с маленькими числами, эпсилон будет маленьким.Если a1 и a2 являются результатом операций с большими числами, эпсилон будет большим.

fabs((a1-a2)/a2) < epsilon

Это немного лучше, поскольку вы хотите масштабировать epsilon на a2.Однако есть деление на 0, если a2 равно 0.

fabs(a1-a2) < fabs(a2)*epsilon

Это немного лучше.Однако это неверно, если a2 равно 0.fabs(a2)*epsilon будет равно 0, и сравнение двух значений a1=0.000000001 и a2=0 всегда будет неудачным.

fabs(a1-a2) < max(fabs(a1), fabs(a2))*epsilon

Это немного лучше.Однако это неверно, поскольку epsilon не пропорционально непрерывно .При кодировании IEEE epsilon пропорционально значению дискретно на основе 2.

fabs(a1-a2) < 2^((int)log2(max(fabs(a1), fabs(a2)))*epsilon

Это выглядит корректно для меня, когда и a1 и a2не равны 0.

Реализация универсального метода может быть:

bool nearly_equal(double a1, double a2, double epsilon)
{
  if (a1 == 0 && a2 == 0)
    return true;

  return std::abs(a1 - a2) < epsilon * pow (2.0, static_cast<int> (std::log2(std::max(std::abs(a1), std::abs(a2)))));
}
2 голосов
/ 21 февраля 2011

Это уже было довольно хорошо решено, но несколько пунктов в порядке:

fabs(a1-a2) < epsilon

сравнивает абсолютную разницу между a1 и a2 с допускомepsilon.Это может быть уместно, если вы знаете масштабирование a priori (например, если a2 на самом деле является константой), но его, как правило, следует избегать, если вы не знаете, насколько велики a1 и a2 are.

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

fabs((a1-a2)/a2) < epsilon

(обратите внимание, что деление составляет внутри абсолютного значения; в противном случае это условие бесполезно для отрицательного a2).Относительная ошибка является более правильной для большинства применений, поскольку она более точно отражает то, как на самом деле происходит округление с плавающей точкой, но есть ситуации, в которых она не работает, и вам необходимо использовать абсолютный допуск (обычно это происходит из-за катастрофическая отмена ).Иногда вы также можете видеть относительные границы ошибок, записанные в этой форме:

fabs(a1-a2) < fabs(a2)*epsilon

, которая часто несколько более эффективна, поскольку позволяет избежать деления.

2 голосов
/ 21 февраля 2011

Первый сравнивает только абсолютные значения, тогда как второй сравнивает относительные значения.Скажите, что для epsilon установлено значение 0.1: этого может быть достаточно, если значения a1 и a2 большие.Однако, если оба значения близки к нулю, первый способ будет считать большинство значений равными.

Это действительно зависит от того, с какими ценностями вы имеете дело.Обязательно рассмотрите случай a2==0, если вы используете несколько более математически обоснованный второй случай.

...