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

Как правильно и стандартно сравнивать поплавки?

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

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

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

4b9b3361

Ответ 1

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

Ответ заключается в использовании моей собственной функции для относительного сравнения:

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

Это наиболее подходящее решение для моих нужд. Однако я написал несколько тестов и других методов сравнения. Надеюсь, это будет полезно для кого-то. 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";
    }
}

Ответ 2

Из Руководство по плавающей запятой:

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

Вы можете использовать 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;
}

Ответ 6

Вы должны использовать стандартное определение в 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();
};

[EDIT: Сделано это немного более читаемо.]

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

Ответ 7

Вот реализация решения @geotavros С++ 11. Он использует новую функцию 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(fabs(f1), fabs(f2)));
}

Ответ 8

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

Выдержки:

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