2011-07-26 11 views
15

Narzędzie do sprawdzania kodu, którego używam, narzeka poniżej, gdy zacznę porównywać dwie wartości zmiennoprzecinkowe za pomocą operatora równości. Jaki jest właściwy sposób i jak to zrobić? Czy istnieje funkcja pomocnika (commons- *), którą mogę ponownie wykorzystać?Porównywanie wartości zmiennoprzecinkowych/podwójnych za pomocą operatora ==

Opis

Nie można porównać wartości zmiennoprzecinkowych za pomocą znaku równości (==) operator

Wyjaśnienie

Porównując wartości zmiennoprzecinkowych przy użyciu funkcji równości (==) Operatory nierówności (! =) nie zawsze są dokładne ze względu na błędy zaokrąglania.

Zalecenie

Porównaj dwie wartości pływaka aby sprawdzić, czy są one zbliżone wartości.

float a; 
float b; 

if(a==b) 
{ 
.. 
} 
+4

Na marginesie, jeśli używasz 'float' i obejmuje on pieniądze (lub naprawdę dowolną liczbę zwykle przedstawianą w postaci liczb dziesiętnych), powinieneś rozważyć opcję' BigDecimal'. –

+0

dobry komentarz. Jestem tego świadomy i używam BigDecimal do wszystkich monetarnych rzeczy. Ale dotyczy to wszystkiego innego. –

+0

Użycie java.lang.Float.compare (float1, float2); // zwraca wartość całkowitą, ale nie można jej użyć zamiast float1 == float2 w warunku if –

Odpowiedz

30

IBM has a recommendation do porównywania dwóch pływa, używając podziału, a nie odejmowania - to ułatwia wybór epsilon, który działa dla wszystkich zakresów wejścia.

if (abs(a/b - 1) < epsilon) 

chodzi o wartości epsilon, by użyć 5.96e-08 jak podano w this Wikipedia table, lub może tę wartość 2x.

+7

Co jeśli 'b == 0'? –

+1

@DrewNouakes, dobre pytanie. Możesz sprawdzić ten warunek za pomocą 'if (abs (b) <= 1.18e-38) i jeśli jest to prawda, zaznacz" a "dla tego samego warunku, aby sprawdzić, czy jest równy. –

+2

Ick. Kod ten nie tylko jest strasznie powolny, ale w wielu przypadkach może być po prostu błędny. Jeśli porównujesz np. x do (y-z), tolerancja musi być oparta na wielkości y i z, a nie na wielkości różnicy i nie ma możliwości, aby kod taki jak powyższy wziął to pod uwagę. Dokładne testy równości między x i y są często odpowiednie, np. ktoś obliczył f (x) i chce wiedzieć, czy należy zawracać sobie głowę obliczaniem f (y) [lub po prostu użyć wartości f (x)]. Niestety, dokładne testy równości są często trudne, chociaż operator '==' jest w większości przypadków wystarczająco dobry. – supercat

6
private static final float EPSILON = <very small positive number>; 

if (Math.abs(a-b) < EPSILON) 
    ... 

Jak zmiennoprzecinkowych oferuje zmienny ale niekontrolowane precyzję (czyli nie można ustawić precyzję inny niż przy wyborze między używaniem double i float), musisz wybrać swoją stałą precyzję do porównań.

Należy zauważyć, że to nie jest prawdziwy operator równoważności, ponieważ nie jest przechodni. Możesz łatwo uzyskać a równy b i b jest równy c, ale a nie jest równy c.

Edycja: również pamiętać, że jeśli a jest ujemny i b jest bardzo duża liczba dodatnia, odejmowanie może przepełnić, a wynik będzie ujemny nieskończoność, ale test będzie nadal działać, jako wartość absolutna minus nieskończoności jest dodatnia nieskończoność, która będzie większa niż EPSILON.

+0

Czy możesz wyjaśnić, jaka powinna być rzeczywista wartość EPSILON? –

+1

Zdefiniowałoby EPSILON jako osobną maksymalną tolerancję dla domeny aplikacji. Może to być dość duża liczba, jeśli porównywane liczby są bardzo duże! :) – Affe

+0

@Affe Dokładnie. Będzie to zależało od samej aplikacji. – biziclop

9

Chce, abyś porównał je z taką dokładnością, jakiej potrzebujesz. Na przykład, jeśli wymagać, że pierwsze 4 cyfry dziesiętne swoich pływaków są równe, wówczas należałoby użyć:

if(-0.00001 <= a-b && a-b <= 0.00001) 
{ 
.. 
} 

czyli

if(Math.abs(a-b) < 0.00001){ ... } 

Jeżeli dodasz żądaną dokładność różnicy tych dwóch numery i porównaj je z dwukrotnością żądanej dokładności.

Cokolwiek uważasz za bardziej czytelne. Ja sam wolę ten pierwszy, ponieważ wyraźnie pokazuje on precyzję, którą dopuszczasz po obu stronach.

a = 5.43421 i b = 5.434205 minie porównanie

+0

jeśli "a" wynosi 2, a "b" wynosi 3, pierwsze porównanie nie powiedzie się (poprawnie), ale drugie przekażą (niepoprawnie). – StriplingWarrior

+1

@StriplingWarrior, masz rację. Miałem na myśli '0 <' – Paulpro

3

Stosować commons-lang

org.apache.commons.lang.math.NumberUtils#compare 

także Commons matematyki (w danej sytuacji bardziej odpowiednie rozwiązanie):

http://commons.apache.org/math/apidocs/org/apache/commons/math/util/MathUtils.html#equals(double, double) 
+0

Ta funkcja nie robi nic, aby złagodzić obawy, które podnosi jego narzędzie. – Affe

+0

@ Który? porównaj - zgadzam się; jest równy w commons-math robi to samo, co inne odpowiedzi, także fajne przeładowane wersje są prezentowane w MathUtils. – Andrey

+0

Tak, podejście zastosowane w MathUtils # equals jest zwykle najlepszym rozwiązaniem, chyba że domena aplikacji definiuje coś bardziej szorstkiego. Pierwszy komentarz dotyczył twojej przededycyjnej odpowiedzi :) – Affe

2

Typ float to wartość przybliżona : - istnieje część wykładnicza i część wartościowa o skończonej dokładności.
Na przykład:

System.out.println((0.6/0.2) == 3); // false 

Istnieje ryzyko, że mały błąd zaokrąglenia można dokonać porównania false, gdy matematycznie powinno być true.

Rozwiązaniem jest porównanie pływaki pozwalające drobna różnica wciąż być „równe”:

static float e = 0.00000000000001f; 
if (Math.abs(a - b) < e) 

Apache commons-math na ratunek: MathUtils.(double x, double y, int maxUlps)

Zwraca true, jeśli oba argumenty są równe lub w ciągu zakres dozwolonego błędu (włącznie). Dwie liczby zmiennoprzecinkowe są uważane za równe, jeśli istnieją między nimi (maxUlps - 1) (lub mniej) liczby zmiennoprzecinkowe, tj. Dwie sąsiednie liczby zmiennoprzecinkowe są uważane za równe.

2

Oto aktualna forma kod realizacja fotografia Math:

private static final int SGN_MASK_FLOAT = 0x80000000; 

public static boolean equals(float x, float y, int maxUlps) { 
    int xInt = Float.floatToIntBits(x); 
    int yInt = Float.floatToIntBits(y); 

    if (xInt < 0) 
     xInt = SGN_MASK_FLOAT - xInt; 

    if (yInt < 0) 
     yInt = SGN_MASK_FLOAT - yInt; 

    final boolean isEqual = Math.abs(xInt - yInt) <= maxUlps; 

    return isEqual && !Float.isNaN(x) && !Float.isNaN(y); 
} 

Daje to liczbę pływaków, które mogą być reprezentowane między dwiema wartościami przy obecnej skali, która powinna działać lepiej niż absolutna epsilon.

1

Zrobiłem to w oparciu o sposób, w jaki implementuje java == dla podwójnych. Konwertuje najpierw na długą liczbę całkowitą IEEE 754, a następnie dokonuje porównania bitowego. Double również zapewnia statyczne doubleToLongBits(), aby uzyskać liczbę całkowitą. Za pomocą bitowego manipulowania możesz "zaokrąglić" mantysy podwójnego przez dodanie 1/2 (jeden bit) i obcięcie.

Zgodnie z obserwacją superkata, funkcja najpierw próbuje prostego porównania == i tylko rund, jeśli to się nie powiedzie. Oto, co wymyśliłem z pewnymi (miejmy nadzieję) pomocnymi komentarzami.

Przeprowadziłem pewne ograniczone testy, ale nie mogę powiedzieć, że wypróbowałem wszystkie przypadki skrajne. Ponadto nie testowałem wydajności. To nie powinno być tak źle.

Właśnie zdałem sobie sprawę, że jest to zasadniczo to samo rozwiązanie, które oferuje Dmitri. Być może nieco bardziej zwięzły.

static public boolean nearlyEqual(double lhs, double rhs){ 
    // This rounds to the 6th mantissa bit from the end. So the numbers must have the same sign and exponent and the mantissas (as integers) 
    // need to be within 32 of each other (bottom 5 bits of 52 bits can be different). 
    // To allow 'n' bits of difference create an additive value of 1<<(n-1) and a mask of 0xffffffffffffffffL<<n 
    // e.g. 4 bits are: additive: 0x10L = 0x1L << 4 and mask: 0xffffffffffffffe0L = 0xffffffffffffffffL << 5 
    //int bitsToIgnore = 5; 
    //long additive = 1L << (bitsToIgnore - 1); 
    //long mask = ~0x0L << bitsToIgnore; 
    //return ((Double.doubleToLongBits(lhs)+additive) & mask) == ((Double.doubleToLongBits(rhs)+additive) & mask); 
    return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0xffffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0xffffffffffffffe0L); 
} 

Poniższa modyfikacja obsługuje zmianę w przypadku, gdy wartość znak jest po obu stronach 0.

return lhs==rhs?true:((Double.doubleToLongBits(lhs)+0x10L) & 0x7fffffffffffffe0L) == ((Double.doubleToLongBits(rhs)+0x10L) & 0x7fffffffffffffe0L); 
0

Istnieje wiele przypadków, w których chce się traktować dwóch liczb zmiennoprzecinkowych jak równy tylko jeśli są absolutnie równoważne, a porównanie "delta" byłoby błędne. Na przykład, jeśli f jest czystą funkcją) i wiadomo, że q = f (x) i y === x, to należy wiedzieć, że q = f (y) bez konieczności obliczania go. Niestety == ma dwie wady w tym zakresie.

  • Jeśli jedna wartość jest dodatnia zero, a druga jest ujemna zero, będą one porównywane jako równe, nawet jeśli niekoniecznie są równoważne. Na przykład jeśli f (d) = 1/d, a = 0 i b = -1 * a, to a == b ale f (a)! = F (b).

  • Jeśli jedna z wartości to NaN, porównanie zawsze da wartość false, nawet jeśli jedna wartość została przypisana bezpośrednio z drugiej.

Chociaż istnieje wiele przypadków, w których sprawdzanie liczb zmiennoprzecinkowych na dokładny równoważność jest słuszne i właściwe, nie jestem pewien, o wszelkich przypadkach, gdy rzeczywista zachowanie == należy uznać za najwłaściwszy. Prawdopodobnie wszystkie testy równoważności powinny być wykonywane za pomocą funkcji, która faktycznie testuje równoważność (na przykład porównując bitowe formularze).

1

Po pierwsze, kilka rzeczy do uwaga:

  • „standardowych” sposób to zrobić, to wybrać stałą epsilon, ale stałe Epsilonów nie działają poprawnie na wszystkie zakresy numer.
  • Jeśli chcesz użyć stałego epsilon sqrt(EPSILON), pierwiastek kwadratowy epsilon z float.h jest ogólnie uważany za dobrą wartość. (pochodzi z niesławnej "pomarańczowej książki", której nazwisko ucieka w tej chwili).
  • Podział na zmiennoprzecinkowe będzie powolny, więc prawdopodobnie będziecie go unikać do porównań, nawet jeśli zachowuje się jak wybieranie epsilonu wykonanego na zamówienie dla wielkości liczb.

Co naprawdę chcesz zrobić? coś takiego:
Porównaj, ile reprezentowalnych liczb zmiennoprzecinkowych różnią się wartości.

Ten kod pochodzi z this naprawdę świetny artykuł Bruce Dawson. Artykuł został zaktualizowany here. Główna różnica polega na tym, że stary artykuł łamie zasadę ścisłego aliasingu. (rzutowanie zmiennoprzecinkowych wskaźników na wskaźnik, dereferencje, pisanie, odlewanie). Podczas gdy purystyczny C/C++ szybko wskaże wadę, w praktyce to działa i uważam, że kod jest bardziej czytelny. Jednak nowy artykuł używa związków, a C/C++ zachowuje swoją godność. Dla zwięzłości podaję kod, który łamie ścisłe aliasing poniżej.

// Usable AlmostEqual function 
bool AlmostEqual2sComplement(float A, float B, int maxUlps) 
{ 
    // Make sure maxUlps is non-negative and small enough that the 
    // default NAN won't compare as equal to anything. 
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); 
    int aInt = *(int*)&A; 
    // Make aInt lexicographically ordered as a twos-complement int 
    if (aInt < 0) 
     aInt = 0x80000000 - aInt; 
    // Make bInt lexicographically ordered as a twos-complement int 
    int bInt = *(int*)&B; 
    if (bInt < 0) 
     bInt = 0x80000000 - bInt; 
    int intDiff = abs(aInt - bInt); 
    if (intDiff <= maxUlps) 
     return true; 
    return false; 
} 

Podstawową ideą w powyższym kodzie jest pierwszym ogłoszeniu, że dany IEEE 754 formacie zmiennoprzecinkowym, {sign-bit, biased-exponent, mantissa}, że numery są uporządkowane leksykograficznie jeśli interpretować jako podpisanych wskazówki wielkości. To znaczy, że bit znaku staje się bitem znaku, a wykładnik zawsze całkowicie przewyższa mantysę w definiowaniu wielkości pływaka, a ponieważ jest on pierwszy w określaniu wielkości liczby interpretowanej jako int.

Tak więc, interpretujemy bitową reprezentację liczby zmiennoprzecinkowej jako int o sygnaturze mag. Następnie konwertujemy ints o sygnowanej magii na dwuskładnikowe ints, odejmując je od 0x80000000, jeśli liczba jest ujemna. Następnie porównujemy te dwie wartości, tak jak każdą z dwóch uzupełniających się sygnatur z dwoma podpisami i sprawdzając, ile wartości różnią się one. Jeśli ta kwota jest mniejsza od progu, który wybierzesz w odniesieniu do liczby reprezentowalnych wartości zmiennoprzecinkowych, wartości mogą się różnić i nadal być traktowane jako równe, wtedy mówisz, że są one "równe". Zauważ, że ta metoda poprawnie pozwala, aby "równe" liczby różniły się większymi wartościami dla pływających o większej wielkości, oraz o mniejszymi wartościami dla mniejszych wartości pływowych.

Powiązane problemy