Как правильно и стандартно сравнить поплавки?
Каждый раз, когда я начинаю новый проект и когда мне нужно сравнить некоторые переменные с плавающей запятой или двойные переменные, я пишу такой код:
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 ответов
Спасибо за ваши ответы, они мне очень помогли. Я прочитал эти материалы: первый и второй
Ответ заключается в использовании моей собственной функции для сравнительного сравнения:
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";
}
}
Из руководства с плавающей точкой:
Это плохой способ сделать это, потому что выбранный фиксированный эпсилон, потому что он "выглядит маленьким", на самом деле может быть слишком большим, когда сравниваемые числа также очень малы. Сравнение вернет "истина" для чисел, которые сильно отличаются. И когда числа очень велики, эпсилон может оказаться меньше наименьшей ошибки округления, так что сравнение всегда возвращает "ложь".
Проблема с "магическим числом" здесь не в том, что он жестко закодирован, а в том, что он "магический": у вас действительно не было причины выбирать 0,000001 вместо 0,000005 или 0,0000000000001, не так ли? Обратите внимание, что float
может приблизительно представлять последние и еще меньшие значения - это только около 7 десятичных знаков после первой ненулевой цифры!
Если вы собираетесь использовать фиксированный эпсилон, вы должны действительно выбрать его в соответствии с требованиями конкретного фрагмента кода, в котором вы его используете. Альтернативой является использование относительной погрешности (см. Ссылку вверху), или, что еще лучше, или сравнение чисел с плавающей точкой.
Стандарт предоставляет значение эпсилон. Оно в <limits>
и вы можете получить доступ к значению по std::numeric_limits<float>::epsilon
а также std::numeric_limits<double>::epsilon
, Там есть и другие значения, но я не проверял, что именно.
Ты можешь использовать 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;
}
Вот реализация решения @geotavros на языке C++ 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(std::fabs(f1), std::fabs(f2)));
}
Вы должны знать, что если вы сравниваете два числа с плавающей точкой на равенство, вы, по сути, делаете неправильные вещи. Добавление коэффициента наклона к сравнению недостаточно хорошо.
Вы должны использовать стандартное определение в 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();
};
[РЕДАКТИРОВАТЬ: сделал его чуть более читабельным.]
Но кроме того, это зависит от того, что вы ищете.
Этот пост содержит подробное объяснение того, как сравнивать числа с плавающей запятой: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
Выдержка:
- Если вы сравниваете ноль, то относительные эпсилоны и сравнения на основе ULP обычно не имеют смысла. Вам нужно будет использовать абсолютный эпсилон, значение которого может быть немного кратным FLT_EPSILON и входные данные для ваших расчетов. Может быть.
- Если вы сравниваете с ненулевым числом, то, вероятно, вам нужны относительные сравнения на основе эпсилонов или ULP. Возможно, вам понадобится небольшое кратное FLT_EPSILON для вашего относительного эпсилона или небольшое количество ULP. Абсолютный эпсилон можно использовать, если вы точно знаете, с каким числом вы сравниваете.