Jak prawidłowo i standardowo porównywać pływaki?
Za każdym razem, gdy uruchamiam nowy projekt i kiedy muszę porównać zmienne float lub double, piszę kod w ten sposób:
if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
fabs(prev.max[i] - cur->max[i]) < 0.000001) {
continue;
}
Następnie chcę pozbyć się tych magicznych zmiennych 0.000001 (i 0.00000000001 dla double) i fabs, więc piszę funkcję inline i niektóre definiuje:
#define FLOAT_TOL 0.000001
Więc zastanawiam się, czy jest jakiś standardowy sposób, aby to zrobić? Może być jakiś standardowy plik nagłówka? Byłoby również miło mieć limity float i double (wartości min i max)
8 answers
From The Floating-Point Guide :
To jest zły sposób, aby to zrobić, ponieważ Poprawiono epsilon wybrany ponieważ " wygląda małe " faktycznie może być o wiele za duże gdy porównywane liczby są bardzo małe. Porównanie zwróci "true" dla liczb, które są zupełnie inne. A kiedy liczby są bardzo duże, epsilon może skończyć się być mniejszy niż najmniejszy błąd zaokrąglania, tak aby porównanie zawsze zwraca "false"
Problem z "magiczną liczbą" nie polega na tym, że jest zakodowana na twardo, ale na tym, że jest" magiczna": tak naprawdę nie miałeś powodu, aby wybrać 0.000001 nad 0.000005 lub 0.000000000001, prawda? Zauważ, że float
może w przybliżeniu reprezentować te ostatnie i jeszcze mniejsze wartości - to tylko około 7 miejsc po przecinku precyzji po pierwszej niezerowej cyfrze!
Jeśli zamierzasz używać stałego epsilonu, powinieneś wybrać go zgodnie z wymaganiami konkretny fragment kodu, w którym go używasz. Alternatywą jest użycie względnego marginesu błędu (szczegóły patrz link u góry) lub, jeszcze lepiej, lub porównaj pływaki jako liczby całkowite .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 19:50:09
Standard dostarcza wartość epsilon. Jest w {[0] } i możesz uzyskać dostęp do wartości przez std::numeric_limits<float>::epsilon
i std::numeric_limits<double>::epsilon
. Są tam inne wartości, ale nie sprawdzałem, co dokładnie jest.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 18:16:45
Powinieneś mieć świadomość, że jeśli porównujesz dwa pływaki dla równości, to z natury rzeczy robią złe rzeczy. Dodawanie współczynnika slop do porównania to za mało.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-28 17:44:06
Możesz użyć std::nextafter
do testowania dwóch double
z najmniejszym epsilonem na wartości (lub współczynniku najmniejszego epsilonu).
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-21 14:38:48
Powinieneś użyć standardowego define w float.h:
#define DBL_EPSILON 2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */
Lub klasa 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: poprawiłem trochę czytelność.]
Ale poza tym, to zależy od tego, czego szukasz.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-30 16:26:09
Dzięki za odpowiedzi, Bardzo mi pomogły. Przeczytałem te materiały: pierwszy i drugi
Odpowiedzią jest użycie mojej własnej funkcji do porównania względnego:
bool areEqualRel(float a, float b, float epsilon) {
return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}
To najbardziej odpowiednie rozwiązanie dla moich potrzeb. Jednak napisałem kilka testów i innych metod porównawczych. Mam nadzieję, że to się komuś przyda. arequalrel przechodzi te testy, inni nie.
#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";
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:04
Oto implementacja c++11 rozwiązania @geotavros. Wykorzystuje nową funkcję std::numeric_limits<T>::epsilon()
oraz fakt, że std::fabs()
i std::fmax()
mają teraz przeciążenia dla float
, double
i 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)));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-14 09:11:40
Ten post zawiera wyczerpujące wyjaśnienie, jak porównywać liczby zmiennoprzecinkowe: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
Fragment:
- jeśli porównujesz z zerem, to względne porównania oparte na epsilonach i ULPs są zwykle bez znaczenia. Musisz użyć absolutny epsilon, którego wartość może być małą wielokrotnością FLT_EPSILON i dane wejściowe do obliczeń. Może.
- jeśli porównujesz z niezerową liczbą, to względne porównania oparte na epsilonach lub ULPs są prawdopodobnie tym, czego chcesz. Będziesz prawdopodobnie potrzebujesz małej wielokrotności FLT_EPSILON dla swojego krewnego epsilon, czyli mała liczba ULP. Absolutny epsilon może być używane, jeśli dokładnie wiesz, z jaką liczbą porównujesz.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-19 05:00:21