Как правильно и стандартно сравнить поплавки? - PullRequest
27 голосов
/ 28 декабря 2010

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

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
    fabs(prev.max[i] - cur->max[i]) < 0.000001) {
        continue;
}

Затем я хочу избавиться от этих магических переменных 0.000001 (и 0.00000000001 для double) и fabs, поэтому я пишу встроенную функцию и некоторые определения:

#define FLOAT_TOL 0.000001

Так мне интересно, есть ли какой-нибудь стандартный способ сделать это? Может быть какой-то стандартный заголовочный файл? Было бы также неплохо иметь плавающие и двойные ограничения (минимальное и максимальное значения)

Ответы [ 8 ]

16 голосов
/ 28 декабря 2010

С Гид с плавающей точкой :

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

Проблема с «магическим числом» здесь не в том, что он жестко закодирован, а в том, что он «магический»: у вас действительно не было причины выбирать 0,000001 вместо 0,000005 или 0,0000000000001, не так ли? Обратите внимание, что float может приблизительно представлять последние и еще меньшие значения - это примерно 7 десятичных знаков точности после первой ненулевой цифры!

Если вы собираетесь использовать фиксированный эпсилон, вам действительно следует выбрать его в соответствии с требованиями конкретного фрагмента кода, в котором вы его используете. Альтернативой является использование относительного запаса ошибок (см. Ссылку вверху), или, что еще лучше, или сравнение чисел с плавающей точкой как .

12 голосов
/ 28 декабря 2010

Стандарт обеспечивает значение эпсилона. Это в <limits>, и вы можете получить доступ к значению с помощью std::numeric_limits<float>::epsilon и std::numeric_limits<double>::epsilon. Там есть и другие значения, но я не проверял, что именно.

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

Вы можете использовать std::nextafter для тестирования двух double с наименьшим эпсилоном по значению (или с коэффициентом наименьшего эпсилона).

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;
}
7 голосов
/ 28 декабря 2010

Вы должны знать, что если вы сравниваете два числа с плавающей точкой на равенство, вы изначально делают не то, что нужно. Добавление коэффициента наклона к сравнению не достаточно хорош.

5 голосов
/ 05 января 2011

Спасибо за ваши ответы, они мне очень помогли.Я прочитал эти материалы: first и second

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

#include <iostream>
#include <limits>
#include <algorithm>

using std::cout;
using std::max;

bool areEqualAbs(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon);
}

bool areEqual(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
}

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

int main(int argc, char *argv[])
{
    cout << "minimum: " << FLT_MIN      << "\n";
    cout << "maximum: " << FLT_MAX      << "\n";
    cout << "epsilon: " << FLT_EPSILON  << "\n";

    float a = 0.0000001f;
    float b = 0.0000002f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
    a = 1000001.f;
    b = 1000002.f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
}
4 голосов
/ 28 декабря 2010

Вы должны использовать стандартное определение в float.h:

#define DBL_EPSILON     2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */

или класс numeric_limits:

// excerpt
template<>
class numeric_limits<float> : public _Num_float_base
{
public:
    typedef float T;

    // return minimum value
    static T (min)() throw();

    // return smallest effective increment from 1.0
    static T epsilon() throw();

    // return largest rounding error
    static T round_error() throw();

    // return minimum denormalized value
     static T denorm_min() throw();
};

[РЕДАКТИРОВАТЬ: Сделано это немного более читабельным.]

Но, кроме того, это зависит от того, что вы ищете.

3 голосов
/ 14 октября 2016

Вот реализация на языке c ++ 11 решения @geotavros. Он использует новую функцию std::numeric_limits<T>::epsilon() и тот факт, что std::fabs() и std::fmax() теперь имеют перегрузки для float, double и long float.

template<typename T>
static bool AreEqual(T f1, T f2) { 
  return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(std::fabs(f1), std::fabs(f2)));
}
1 голос
/ 19 июля 2013

Этот пост содержит подробное объяснение того, как сравнивать числа с плавающей запятой: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

Выдержка:

  • Если вы сравниваете с нулем, то относительные сравнения на основе эпсилонов и ULP обычно не имеют смысла. Вам нужно будет использовать абсолютный эпсилон, значение которого может быть небольшим кратным FLT_EPSILON и входные данные для вашего расчета. Может быть.
  • Если вы сравниваете с ненулевым числом, то, скорее всего, вам нужны относительные сравнения на основе эпсилонов или ULP. Вы будете вероятно, вам нужно несколько кратных FLT_EPSILON для вашего родственника эпсилон или небольшое количество ULP. Абсолютный эпсилон может быть используется, если вы точно знаете, с каким числом вы сравниваете.
...