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)

Author: mskfisher, 2010-12-28

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 .

 15
Author: Michael Borgwardt,
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.

 12
Author: Puppy,
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.

 7
Author: ddyer,
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;
}
 7
Author: Daniel Laügt,
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.
 4
Author: 0xbadf00d,
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";
    }
}
 3
Author: Dmitriy,
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)));
}
 3
Author: Jelle van Campen,
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.
 1
Author: blufiro,
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