2013-07-15 17 views
16

Prawidłowe przetestowanie dwóch liczb zmiennoprzecinkowych dla równości jest czymś, czego wielu ludzi, w tym mnie, nie w pełni rozumie. Dzisiaj jednak pomyślałem o tym, jak niektóre standardowe pojemniki definiują równość pod względem operator<. Zawsze widzę ludzi mających problemy z równością, ale nigdy z innymi porównaniami relacyjnymi. Jest ich nawet silent versions, które obejmują wszystko oprócz równości i nierówności.Jeśli operator <działa poprawnie dla typów zmiennoprzecinkowych, dlaczego nie możemy go użyć do testowania równości?

Zakładając operator< działa „prawidłowo”, w przeciwieństwie do operator==, dlaczego my nie mogliśmy to zrobić:

bool floateq(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

W rzeczywistości, ja uruchomić test z dodatkowym przeciążenia dla deblu, jak widać here, i to wydaje się, że te same pułapki porównać je z operator==:

std::cout << "float->double vs double: " 
      << floateq(static_cast<double>(0.7f), 0.7) << " " 
      << (static_cast<double>(0.7f) == 0.7) << "\n"; 

wyjściowy:

float-> double vs podwójne: 0 0

jestem martwić się o używaniu wszystkie operatorów porównania, czy istnieje jakiś inny aspekt porównywania liczb zmiennoprzecinkowych, że nie jestem zrozumieniem prawidłowo?

+3

Nie ma nic złego w porównaniu wartości zmiennoprzecinkowych jako takich. Problem polega na tym, że wynik może nie działać zgodnie z oczekiwaniami. –

Odpowiedz

14

==, <, >, <=, >= i != operatorzy działać dobrze z liczb zmiennoprzecinkowych.

Wydaje się, że założenie, że niektóre rozsądne wdrożenie <powinien porównać (podwójne) 0.7f równy 0,7. Nie o to chodzi.Jeśli odrzucisz 0.7f na double, otrzymasz 0x1.666666p-1. Jednak 0.7 jest równy 0x1.6666666666666p-1. Nie są to liczby równe; w rzeczywistości, (double)0.7f jest znacznie mniejsza niż 0.7 --- byłoby śmieszne dla nich porównywać równe.

Podczas pracy z liczbami zmiennoprzecinkowymi należy pamiętać, że są liczbami zmiennoprzecinkowymi, a nie liczbami rzeczywistymi lub liczbami wymiernymi lub jakąkolwiek inną rzeczą. Musisz wziąć pod uwagę ich właściwości, a nie właściwości, które każdy chce mieć. Zrób to i automatycznie unikaj większości powszechnie wymienianych "pułapek" pracy z liczbami zmiennoprzecinkowymi.

+0

Myślę, że to jest dokładnie to, czego potrzebowałem. Nie kliknęło to, że rzucanie 'float' pozostawia dodatkową precyzję inną niż" double ", która miała pierwotnie. Tak więc jeden musi być mniejszy od drugiego, a zatem nie równy. To też wstyd, ponieważ dzięki temu moje życie stałoby się o wiele łatwiejsze, gdyby to zadziałało: p – chris

+0

Świetne pytanie, a także świetna odpowiedź. Dla każdego, kto pisze oprogramowanie do czynienia z pieniędzmi, używam porównania "if (abs (a-b) <0,001)". Zwykle systemy finansowe wynoszą tylko pół centa lub co najwyżej jedną dziesiątą. Zdecydowanie ważne jest, aby dokładnie wyjaśnić. –

+0

Wykonanie obliczeń finansowych za pomocą liczb zmiennoprzecinkowych jest bardzo ryzykowne. Zwykle jest to zrobione z _integer_ liczbą pensów lub z _decimalną reprezentacją. Tak czy inaczej, nadal musisz zachować szczególną ostrożność. –

1

Poniższy kod (które zmieniły się kompiluje: Konkretnie wywołanie floateq została zmieniona na floatcmp) wypisuje float->double vs double: 1 0, nie 0 0 (jak można by oczekiwać przy porównywaniu tych dwóch wartości jako pływaki).

#include <iostream> 

bool floatcmp(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

int main() 
{ 
    std::cout << "float->double vs double: " 
       << floatcmp(static_cast<double>(0.7f), 0.7) << " " 
       << (static_cast<double>(0.7f) == 0.7) << "\n"; 
} 

Jednak co ważne dla standardowej biblioteki jest to, że operator< określa ścisłe słaby zamawiania, które w rzeczywistości robi dla pływających typy punktów.

Problem z równością polega na tym, że dwie wartości mogą wyglądać tak samo zaokrąglone, aby powiedzieć 4 lub 6 miejsc, ale w rzeczywistości są zupełnie inne i porównywać je jako nierówne.

+0

Przykro nam, że wysłany przeze mnie fragment kodu został pobrany głównie z mojego testu, z którym był powiązany. To ma przeciążenie dla dwóch pływaków i przeciążenie dla dwóch podwójnych. Poruszałem to pytanie, aby lepiej to odzwierciedlić i zachować spójność nazwy. – chris

+0

Odnośnie faktycznej odpowiedzi, czy nie powinno się zarzucać tej odrobiny puszystości, ile miejsc dziesiętnych znajdzie dla siebie operator ' chris

+0

'operator <' nie jest ścisłym słabym porządkiem dla typów zmiennoprzecinkowych, ponieważ '! (1 tmyklebu

-2

Ogólnie wszystkie operacje porównywania liczb zmiennoprzecinkowych powinny być wykonywane w ramach określonego limitu dokładności. W przeciwnym razie możesz zostać ugryziony przez skumulowany błąd zaokrąglenia, którego nie widać z małą dokładnością, ale zostanie uwzględniony przez operatorów porównania. Po prostu nie ma większego znaczenia przy sortowaniu.

Kolejny przykład kodu, który pokazuje, że twoje porównanie nie działa (http://ideone.com/mI4S76).

#include <iostream> 

bool floatcmp(float a, float b) { 
    //check NaN 
    return !(a < b) && !(b < a); 
} 

int main() { 
    using namespace std; 

    float a = 0.1; 
    float b = 0.1; 

    // Introducing rounding error: 
    b += 1; 
    // Just to be sure change is not inlined 
    cout << "B after increase = " << b << endl; 
    b -= 1; 
    cout << "B after decrease = " << b << endl; 

    cout << "A " << (floatcmp(a, b) ? "equals" : "is not equal to") << "B" << endl; 
} 

wyjściowa:

B after increase = 1.1 
B after decrease = 0.1 
A is not equal toB 
+0

Gdzie jest przechowywana losowa przeszłość-precyzja części pływaka? Widzę tylko bit znaku, wykładnik i znaczenie. – tmyklebu

+0

Cóż, muszę przyznać, że użyte sformułowanie jest frustrujące. Zaktualizowano. – biocomp

+2

Kłopot ze zaktualizowaną odpowiedzią polega na tym, że musisz jakoś wybrać tę tolerancję.Nie może być zbyt duży, lub będziesz fałszywie twierdził, że dwie różne rzeczy są równe i nie może być zbyt małe, lub będziesz fałszywie twierdzić, że dwie "równe" rzeczy są różne. Zbieranie tolerancji, kiedy można to zrobić w ogóle, zwykle wymaga starannej analizy danych wejściowych i wyniku zaokrągleń wynikających z obliczeń, które wykonujesz na danym wejściu, w którym to momencie prawdopodobnie jest oczywiste, że będziesz musiał porównać w tolerancja. – tmyklebu

1

Float i double są zarówno w binarnym odpowiedniku notacji naukowej, ze stałą liczbą znaczących bitów. Jeśli wynik nieskończonej dokładności obliczeń nie jest dokładnie reprezentowalny, rzeczywisty wynik jest najbliższy dokładnie reprezentowanemu.

Są to dwie poważne pułapki.

  1. Wiele prostych, krótkich pojęć dziesiętnych, jak np. 0,1, nie jest dokładnie przedstawianych w float lub double.
  2. Dwa wyniki, które byłyby równe w arytmetyce liczby rzeczywistej, mogą być różne w arytmetyzacji zmiennoprzecinkowej. Na przykład, arytmetyce zmiennoprzecinkowej nie jest łączne - (a + b) + c niekoniecznie jest taka sama jak a + (b + c)

Musisz wybrać tolerancję dla porównań, który jest większy niż oczekiwany błąd zaokrąglenia, ale na tyle małe, że dopuszczalne jest w swojej program traktujący liczby w granicach tolerancji jako równe.

Jeśli nie ma takiej tolerancji, oznacza to, że używasz niewłaściwego typu zmiennoprzecinkowego lub w ogóle nie należy używać zmiennoprzecinkowego. 32-bitowy IEEE 754 ma tak ograniczoną precyzję, że znalezienie właściwej tolerancji może być naprawdę trudne. Zwykle 64-bit to znacznie lepszy wybór.

2

Podczas używania liczb zmiennoprzecinkowych operatory relacyjne mają znaczenie, ale ich znaczenie niekoniecznie jest zgodne z zachowaniem rzeczywistych liczb.

Jeśli wartości zmiennoprzecinkowe są używane do reprezentowania rzeczywistych liczb (ich normalne celu), operatorzy często zachowują się w sposób następujący:

  • x > y i x >= y zarówno oznaczać, że ilość numeryczny który x ma reprezentacja jest prawdopodobnie większa niż y, aw najgorszym prawdopodobnie nie mniej niż y.

  • x < y i x <= y zarówno oznaczać, że ilość numeryczny który x ma reprezentować prawdopodobnie mniej niż niż y, i to w najgorszym prawdopodobnie nie dużo większe niż y.

  • x == y oznacza, że ​​ilości liczbowy x i y stanowią są nie do odróżnienia od siebie

Należy zauważyć, że jeśli x jest typu float i y jest typu double powyższe znaczenie zostanie osiągnięty jeśli argument double jest rzutowany na float. Jednak w przypadku braku konkretnej obsady, C i C++ (a także wiele innych języków) konwertuje operand float na double przed wykonaniem porównania. Konwersja taka znacznie zmniejszy prawdopodobieństwo, że operandy zostaną zgłoszone jako "nie do odróżnienia", ale znacznie zwiększy prawdopodobieństwo, że porównanie da wynik sprzeczny z zamierzonymi liczbami. Weźmy na przykład

float f = 16777217; 
double d = 16777216.5; 

Jeśli oba argumenty są rzutowane na float porównanie wskazuje, że wartości są nie do odróżnienia. Jeśli są one rzutowane na double, porównanie będzie wskazywać, że d jest większe, chociaż wartość f ma być reprezentowana jest nieco większa. Jako bardziej skrajny przykład:

float f = 1E20f; 
float f2 = f*f; 
double d = 1E150; 
double d2 = d*d; 

Float f2 zawiera najlepsze float reprezentację 1E40. Podwójnie d2 zawiera najlepszą reprezentację 01E1E400. Liczba liczbowa reprezentowana przez d2 is hundreds of orders of magnitude greater than that represented by f2 , but (podwójne) f2> d2 . By contrast, converting both operands to float would yield f2 == (zmiennoprzecinkowe) d2`, poprawnie zgłaszające, że wartości są nieodróżnialne.

PS - Doskonale zdaję sobie sprawę, że standardy IEEE wymagają, aby obliczenia były wykonywane tak, jakby wartości zmiennoprzecinkowe reprezentowały dokładne ułamki mocy dwóch, ale niewiele osób widziało kod float f2 = f1/10.0; jako "Ustaw f2 na reprezentowalną moc -z dwóch frakcji, która jest najbliższa jednej dziesiątej z f1 ". Celem tego kodu jest sprawienie, by f2 było dziesiątą częścią f1. Z powodu niedokładności kod nie może doskonale spełnić tego celu, ale w większości przypadków bardziej przydatne jest uznawanie liczb zmiennoprzecinkowych za reprezentujące rzeczywiste wielkości liczbowe niż uważanie ich za frakcje o potędze dwóch.

Powiązane problemy