Przede wszystkim wartości zmiennoprzecinkowe nie są „losowe” w swoim zachowaniu. Dokładne porównanie może i ma sens w wielu rzeczywistych zastosowaniach. Ale jeśli zamierzasz użyć zmiennoprzecinkowego, musisz wiedzieć, jak to działa. Błąd polegający na założeniu, że zmiennoprzecinkowe działa jak liczby rzeczywiste, spowoduje, że kod szybko się zepsuje. Błąd polegający na założeniu, że wyniki zmiennoprzecinkowe są powiązane z dużym losowym rozmyciem (jak sugeruje większość odpowiedzi tutaj), otrzyma kod, który wydaje się działać na początku, ale ostatecznie zawiera błędy dużej wielkości i niedziałające przypadki narożników.
Przede wszystkim, jeśli chcesz programować z liczbą zmiennoprzecinkową, powinieneś przeczytać to:
Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej
Tak, przeczytaj wszystko. Jeśli jest to zbyt duże obciążenie, do obliczeń należy używać liczb całkowitych / ustalonego punktu, dopóki nie będzie czasu, aby je odczytać. :-)
To powiedziawszy, największe problemy z dokładnymi porównaniami zmiennoprzecinkowymi sprowadzają się do:
Fakt, że wiele wartości, które możesz zapisać w źródle lub wczytać za pomocą scanf
lub strtod
, nie istnieje jako wartości zmiennoprzecinkowe i po cichu są konwertowane na najbliższe przybliżenie. O tym mówiła odpowiedź demon9733.
Fakt, że wiele wyników jest zaokrąglanych z powodu braku wystarczającej dokładności do przedstawienia rzeczywistego wyniku. Prostym przykładem, w którym można to zobaczyć, jest dodawanie x = 0x1fffffe
i y = 1
jako zmiennoprzecinkowe. Tutaj x
ma 24 bity precyzji w mantysie (ok) i y
ma tylko 1 bit, ale kiedy je dodasz, ich bity nie nakładają się na siebie, a wynik wymagałby 25 bitów precyzji. Zamiast tego zostaje zaokrąglony ( 0x2000000
w domyślnym trybie zaokrąglania).
Fakt, że wiele wyników jest zaokrąglanych z powodu nieskończonej liczby miejsc dla prawidłowej wartości. Obejmuje to zarówno racjonalne wyniki, takie jak 1/3 (które znasz od miejsc po przecinku, gdzie zajmuje nieskończenie wiele miejsc), ale także 1/10 (co również zajmuje nieskończenie wiele miejsc w systemie binarnym, ponieważ 5 nie jest potęgą 2), a także irracjonalne wyniki, takie jak pierwiastek kwadratowy z czegoś, co nie jest idealnym kwadratem.
Podwójne zaokrąglenie. W niektórych systemach (szczególnie x86) wyrażenia zmiennoprzecinkowe są oceniane z większą precyzją niż ich typy nominalne. Oznacza to, że gdy nastąpi jeden z powyższych rodzajów zaokrąglania, otrzymasz dwa kroki zaokrąglania, najpierw zaokrąglenie wyniku do typu o wyższej precyzji, a następnie zaokrąglenie do typu końcowego. Jako przykład rozważmy, co dzieje się w systemie dziesiętnym, jeśli zaokrąglisz 1,49 do liczby całkowitej (1), w porównaniu do tego, co się stanie, jeśli najpierw zaokrąglisz go do jednego miejsca po przecinku (1,5), a następnie zaokrąglisz ten wynik do liczby całkowitej (2). Jest to w rzeczywistości jeden z najgorszych obszarów, z którymi należy się zmierzyć w liczbach zmiennoprzecinkowych, ponieważ zachowanie kompilatora (szczególnie w przypadku błędnych, niezgodnych kompilatorów, takich jak GCC) jest nieprzewidywalne.
Funkcje transcendentalne ( trig
, exp
, log
, itd.) Nie są podane na poprawne zaokrąglone wyniki; wynik jest po prostu określony jako poprawny w obrębie jednej jednostki w ostatnim miejscu precyzji (zwykle określanym jako 1ulp ).
Pisząc kod zmiennoprzecinkowy, należy pamiętać o tym, co robisz z liczbami, które mogą powodować niedokładność wyników, i odpowiednio dokonywać porównań. Często ma sens porównywanie z „epsilonem”, ale ten epsilon powinien opierać się na wielkości porównywanych liczb , a nie na absolutnej stałej. (W przypadkach, w których działałaby absolutna stała epsilon, jest to silnie wskazujące, że punkt stały, a nie zmiennoprzecinkowy, jest właściwym narzędziem do pracy!)
Edycja: W szczególności sprawdzenie epsilon w zależności od wielkości powinno wyglądać mniej więcej tak:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Gdzie FLT_EPSILON
jest stała od float.h
(wymienić go DBL_EPSILON
na double
s lub LDBL_EPSILON
dla long double
S) i K
jest stałą wybrać takie, że skumulowany błąd swoich obliczeniach jest zdecydowanie ograniczona przez K
jednostki na ostatnim miejscu (a jeśli nie jesteś pewien, że masz błąd powiązane obliczenia, zrób K
kilka razy większe niż to, co mówią twoje obliczenia).
Na koniec zauważ, że jeśli go użyjesz, może być wymagana szczególna ostrożność w pobliżu zera, ponieważ FLT_EPSILON
nie ma to sensu w przypadku denormali. Szybkim rozwiązaniem byłoby, aby:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
i podobnie zamień, DBL_MIN
jeśli używasz podwójnych.
fabs(x+y)
jest problematyczne, jeślix
iy
(może) mieć inny znak. Mimo to dobra odpowiedź na falę porównań kultu ładunku.x
iy
mają inny znak, nie ma problemu. Z prawej strony będzie „zbyt mały”, ale ponieważx
iy
mieć inny znak, nie należy porównać równe w każdym razie. (Chyba, że są tak małe, że są denormalne, ale wtedy druga sprawa je łapie)Ponieważ 0 jest dokładnie reprezentowalne jako liczba zmiennoprzecinkowa IEEE754 (lub przy użyciu dowolnej innej implementacji liczb fp, z którymi kiedykolwiek pracowałem), porównanie z 0 jest prawdopodobnie bezpieczne. Możesz zostać ugryziony, jeśli twój program wyliczy (na przykład
theView.frame.origin.x
) wartość, która według ciebie powinna wynosić 0, ale której obliczenie nie może zagwarantować 0.Aby wyjaśnić trochę, obliczenia takie jak:
(chyba że Twój język lub system jest uszkodzony) utworzy taką wartość, że (areal == 0.0) zwróci true, ale inne obliczenia, takie jak
może nie.
Jeśli możesz się upewnić, że twoje obliczenia dają wartości 0 (a nie tylko to, że powinny one wynosić 0), możesz przejść dalej i porównać wartości fp z 0. Jeśli nie możesz się upewnić w wymaganym stopniu , najlepiej trzymać się zwykłego podejścia „równości tolerowanej”.
W najgorszych przypadkach nieostrożne porównanie wartości fp może być bardzo niebezpieczne: pomyśl awionikę, prowadzenie broni, operacje w elektrowniach, nawigację w pojazdach, prawie każdą aplikację, w której obliczenia spełniają rzeczywisty świat.
Dla Angry Birds nie jest tak niebezpieczne.
źródło
1.30 - 2*(0.65)
jest doskonałym przykładem wyrażenia, które ewaluuje do wartości 0,0, jeśli twój kompilator implementuje IEEE 754, ponieważ liczby podwójne reprezentowane jako0.65
i1.30
mają takie same znaczenia, a mnożenie przez dwa jest oczywiście dokładne.Chcę dać nieco inną odpowiedź niż inne. Świetnie nadają się do odpowiedzi na zadane pytanie, ale prawdopodobnie nie do tego, co musisz wiedzieć lub jaki jest twój prawdziwy problem.
Zmienny punkt w grafice jest w porządku! Ale prawie nie ma potrzeby nigdy porównywać pływaków bezpośrednio. Dlaczego miałbyś to robić? Grafika używa pływaków do definiowania interwałów. Porównywanie, czy liczba zmiennoprzecinkowa mieści się w przedziale zdefiniowanym również przez zmiennoprzecinkową, jest zawsze dobrze zdefiniowana i wymaga jedynie spójności, niedokładności lub precyzji! Tak długo, jak piksel (który jest również interwałem!) Może być przypisany, to wszystko potrzebuje grafiki.
Więc jeśli chcesz sprawdzić, czy twój punkt znajduje się poza zakresem [0.. szerokości [to jest w porządku. Upewnij się tylko, że konsekwentnie definiujesz włączenie. Na przykład zawsze określ, że wewnątrz jest (x> = 0 i& x <szerokość). To samo dotyczy testów skrzyżowań lub trafień.
Jeśli jednak nadużywasz współrzędnych grafiki jako jakiegoś rodzaju flagi, np. Aby sprawdzić, czy okno jest zadokowane, czy nie, nie powinieneś tego robić. Zamiast tego użyj flagi boolowskiej, która jest oddzielna od warstwy prezentacji graficznej.
źródło
Porównywanie do zera może być bezpieczną operacją, o ile zero nie było wartością obliczoną (jak zauważono w powyższej odpowiedzi). Powodem tego jest to, że zero jest liczbą doskonale reprezentowalną w liczbach zmiennoprzecinkowych.
Mówiąc o doskonale reprezentowanych wartościach, otrzymujesz 24 bity zasięgu w ujęciu potęgi dwóch (pojedyncza precyzja). Zatem 1, 2, 4 są doskonale reprezentowalne, podobnie jak .5, .25 i .125. Dopóki wszystkie twoje ważne bity są 24-bitowe, jesteś złoty. Tak więc 10.625 można dokładnie przedstawić.
To świetnie, ale szybko rozpadnie się pod presją. Przychodzą mi na myśl dwa scenariusze: 1) Gdy w grę wchodzą obliczenia. Nie ufaj, że sqrt (3) * sqrt (3) == 3. Po prostu nie będzie tak. I prawdopodobnie nie będzie to epsilon, jak sugerują niektóre inne odpowiedzi. 2) Gdy w grę wchodzi jakikolwiek inny niż power-of-2 (NPOT). Może to zabrzmieć dziwnie, ale 0.1 jest nieskończoną serią binarną, a zatem wszelkie obliczenia obejmujące taką liczbę będą od początku nieprecyzyjne.
(Aha, a pierwotne pytanie wspomniało o porównaniach do zera. Nie zapominaj, że -0.0 jest również całkowicie poprawną wartością zmiennoprzecinkową.)
źródło
[Właściwa odpowiedź przeskakuje nad wyborem
K
. WybieranieK
kończy się tak samo ad hoc jak wybieranie,VISIBLE_SHIFT
ale wybieranieK
jest mniej oczywiste, ponieważ w przeciwieństwie doVISIBLE_SHIFT
tego nie jest oparte na żadnej właściwości wyświetlania. Wybierz swoją truciznę - wybierzK
lub wybierzVISIBLE_SHIFT
. Ta odpowiedź opowiada się za wyborem,VISIBLE_SHIFT
a następnie pokazuje trudność w wyborzeK
]Właśnie z powodu błędów okrągłych nie należy używać porównywania „dokładnych” wartości dla operacji logicznych. W konkretnym przypadku pozycji na wyświetlaczu wizualnym nie ma znaczenia, czy pozycja wynosi 0,0, czy 0,0000000003 - różnica jest niewidoczna dla oka. Więc twoja logika powinna wyglądać następująco:
Jednak ostatecznie „niewidoczny dla oka” będzie zależeć od właściwości ekranu. Jeśli możesz górną granicę wyświetlacza (powinieneś być w stanie); następnie wybierz
VISIBLE_SHIFT
ułamek tej górnej granicy.Teraz „poprawna odpowiedź” zależy od tego,
K
więc zbadajmy sposób wybieraniaK
. „Właściwa odpowiedź” powyżej mówi:Więc potrzebujemy
K
. Jeśli uzyskanieK
jest trudniejsze, mniej intuicyjne niż wybranie mojego,VISIBLE_SHIFT
wtedy zdecydujesz, co będzie dla ciebie odpowiednie. Aby to ustalićK
, napiszemy program testowy, który analizuje wieleK
wartości, abyśmy mogli zobaczyć, jak się zachowuje. Powinno być oczywiste, jak wybraćK
, jeśli można zastosować „właściwą odpowiedź”. Nie?Wykorzystamy jako szczegóły „właściwej odpowiedzi”:
Spróbujmy wszystkich wartości K:
Ach, więc K powinien wynosić 1e16 lub więcej, jeśli chcę, aby 1e-13 było „zero”.
Powiedziałbym, że masz dwie opcje:
K
.źródło
K
która jest trudna i nie intuicyjna w wyborze.Prawidłowe pytanie: jak porównać punkty w Cocoa Touch?
Prawidłowa odpowiedź: CGPointEqualToPoint ().
Inne pytanie: czy dwie obliczone wartości są takie same?
Odpowiedź zamieszczona tutaj: nie są.
Jak sprawdzić, czy są blisko? Jeśli chcesz sprawdzić, czy są blisko, nie używaj CGPointEqualToPoint (). Ale nie sprawdzaj, czy są blisko. Rób coś, co ma sens w prawdziwym świecie, na przykład sprawdzając, czy punkt znajduje się poza linią lub czy znajduje się w kuli.
źródło
Ostatnim razem, gdy sprawdzałem standard C, nie było wymogu, aby operacje zmiennoprzecinkowe na liczbach podwójnych (w sumie 64 bity, 53-bitowa mantysa) były dokładne z większą dokładnością. Jednak niektóre urządzenia mogą wykonywać operacje w rejestrach z większą precyzją, a wymaganie to zostało zinterpretowane jako brak wymogu czyszczenia bitów niższego rzędu (poza dokładnością liczb ładowanych do rejestrów). Dzięki temu można uzyskać nieoczekiwane wyniki takich porównań w zależności od tego, co pozostało w rejestrach od tego, kto spał ostatni.
To powiedziawszy, i pomimo moich starań, aby go zniszczyć, gdy tylko go zobaczę, strój, w którym pracuję, ma dużo kodu C skompilowanego za pomocą gcc i działającego na Linuksie, i od bardzo dawna nie zauważyliśmy żadnego z tych nieoczekiwanych rezultatów. . Nie mam pojęcia, czy dzieje się tak, ponieważ gcc usuwa dla nas bity niskiego rzędu, 80-bitowe rejestry nie są używane do tych operacji na nowoczesnych komputerach, standard został zmieniony, czy co. Chciałbym wiedzieć, czy ktoś może zacytować rozdział i wiersz.
źródło
Możesz użyć takiego kodu do porównania liczby zmiennoprzecinkowej z zerem:
Porówna się to z dokładnością 0,1, co w tym przypadku wystarcza dla CGFloat.
źródło
int
bez ubezpieczeniatheView.frame.origin.x
znajduje się w / w pobliżu tego zakresuint
prowadzi do nieokreślonego zachowania (UB) - lub w tym przypadku w 1/100 zakresuint
.}
źródło
Korzystam z następującej funkcji porównania, aby porównać liczbę miejsc po przecinku:
źródło
Powiedziałbym, że właściwą rzeczą jest zadeklarowanie każdej liczby jako obiektu, a następnie zdefiniowanie w tym obiekcie trzech rzeczy: 1) operatora równości. 2) metoda setAcceptableDifference. 3) sama wartość. Operator równości zwraca wartość true, jeśli różnica bezwzględna dwóch wartości jest mniejsza niż wartość ustawiona jako akceptowalna.
Możesz podklasować obiekt, aby dopasować go do problemu. Na przykład okrągłe pręty metalowe o długości od 1 do 2 cali można uznać za jednakowej średnicy, jeśli ich średnice różnią się o mniej niż 0,0001 cala. Można więc wywołać setAcceptableDifference z parametrem 0.0001, a następnie z pewnością użyć operatora równości.
źródło