Jakie są uzasadnienia dla wszystkich porównań zwracających wartość false dla wartości NaN IEEE754?

267

Dlaczego porównania wartości NaN zachowują się inaczej niż wszystkie inne wartości? Oznacza to, że wszystkie porównania z operatorami ==, <=,> =, <,> gdzie jedna lub obie wartości to NaN, zwraca false, w przeciwieństwie do zachowania wszystkich innych wartości.

Podejrzewam, że upraszcza to w pewien sposób obliczenia numeryczne, ale nie mogłem znaleźć jednoznacznie uzasadnionego powodu, nawet w Notach do wykładu na temat statusu IEEE 754 autorstwa Kahana, które szczegółowo omawiają inne decyzje projektowe.

To odbiegające zachowanie powoduje problemy przy prostym przetwarzaniu danych. Na przykład, podczas sortowania listy rekordów w polu o wartościach rzeczywistych w programie C muszę napisać dodatkowy kod, aby obsługiwać NaN jako element maksymalny, w przeciwnym razie algorytm sortowania mógłby się pomylić.

Edycja: Jak dotąd wszystkie odpowiedzi dowodzą, że porównywanie NaN nie ma sensu.

Zgadzam się, ale to nie znaczy, że poprawna odpowiedź jest fałszywa, raczej byłaby to Not-a-Boolean (NaB), która na szczęście nie istnieje.

Zatem wybór zwracania prawdy lub fałszu dla porównań jest moim zdaniem arbitralny, a dla ogólnego przetwarzania danych byłoby korzystne, gdyby przestrzegał zwykłych praw (zwrotność ==, trikotomia <, ==,>), aby nie było struktur danych które opierają się na tych prawach, stają się zdezorientowane.

Proszę więc o konkretną korzyść z łamania tych praw, a nie tylko filozoficzne rozumowanie.

Edycja 2: Wydaje mi się, że teraz rozumiem, dlaczego zwiększenie NaN byłoby złym pomysłem, co zakłóciłoby obliczanie górnych limitów.

NaN! = NaN może być pożądane, aby uniknąć wykrycia zbieżności w pętli, takiej jak

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

które jednak lepiej napisać porównując różnicę bezwzględną z niewielkim limitem. Więc IMHO jest to stosunkowo słaby argument za przerwaniem refleksyjności w NaN.

starblue
źródło
2
Gdy NaN wejdzie do obliczeń, zwykle nigdy nie opuści, więc test konwergencji stałby się nieskończoną pętlą. Zwykle lepiej jest zgłosić niepowodzenie konwergencji do procedury wywołującej, prawdopodobnie zwracając NaN. Zatem struktura pętli zwykle stałaby się czymś podobnym while (fabs(x - oldX) > threshold), wychodząc z pętli, jeśli nastąpi konwergencja lub NaN wejdzie do obliczeń. Wykrywanie NaN i odpowiednie rozwiązanie nastąpiłoby poza pętlą.
Stephen Canon
1
Gdyby NaN był minimalnym elementem zamówienia, pętla while nadal działałaby.
starblue,
2
Jedzenie do przemyślenia: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf strona 10
starblue

Odpowiedzi:

535

Byłem członkiem komitetu IEEE-754, postaram się trochę wyjaśnić.

Po pierwsze, liczby zmiennoprzecinkowe nie są liczbami rzeczywistymi, a arytmetyka zmiennoprzecinkowa nie spełnia aksjomatów arytmetyki rzeczywistej. Trichotomy nie jest jedyną właściwością prawdziwej arytmetyki, która nie obowiązuje dla liczb zmiennoprzecinkowych, ani nawet najważniejszą. Na przykład:

  • Dodawanie nie jest skojarzone.
  • Prawo dystrybucyjne nie obowiązuje.
  • Istnieją liczby zmiennoprzecinkowe bez odwrotności.

Mógłbym kontynuować. Nie jest możliwe określenie typu arytmetycznego o stałym rozmiarze, który spełnia wszystkie właściwości prawdziwej arytmetyki, które znamy i kochamy. Komitet 754 musi zdecydować o wygięciu lub złamaniu niektórych z nich. Kieruje się to kilkoma prostymi zasadami:

  1. Kiedy możemy, dopasowujemy zachowanie prawdziwej arytmetyki.
  2. Kiedy nie możemy, staramy się, aby naruszenia były jak najbardziej przewidywalne i jak najłatwiejsze do zdiagnozowania.

Jeśli chodzi o Twój komentarz „nie oznacza to, że poprawna odpowiedź jest fałszywa”, jest to błąd. Predykat (y < x)pyta, czy yjest mniejszy niż x. Jeśli yjest NaN, to nie mniej niż wartość zmiennoprzecinkową x, więc odpowiedź jest zawsze fałszywe.

Wspomniałem, że trikotomia nie dotyczy wartości zmiennoprzecinkowych. Istnieje jednak podobna właściwość. Klauzula 5.11 ust. 2 normy 754-2008:

Możliwe są cztery wzajemnie wykluczające się relacje: mniejszy, równy, większy niż i nieuporządkowany. Ostatni przypadek powstaje, gdy co najmniej jednym operandem jest NaN. Każdy NaN porównuje się nieuporządkowany ze wszystkim, w tym samym sobą.

Jeśli chodzi o pisanie dodatkowego kodu do obsługi NaNs, zazwyczaj jest możliwe (choć nie zawsze łatwe) takie ustrukturyzowanie kodu, aby NaN przechodziły poprawnie, ale nie zawsze tak jest. Kiedy tak nie jest, może być potrzebny dodatkowy kod, ale jest to niewielka cena za wygodę, jaką zamknięcie algebraiczne wniosło do arytmetyki zmiennoprzecinkowej.


Dodatek: Wielu komentatorów twierdziło, że bardziej przydatne byłoby zachowanie refleksyjności równości i trikotomii na tej podstawie, że przyjęcie NaN! = NaN wydaje się nie zachowywać żadnego znanego aksjomatu. Przyznaję, że mam sympatię do tego punktu widzenia, więc pomyślałem, że wrócę do tej odpowiedzi i przedstawię nieco więcej kontekstu.

Rozumiem z rozmowy z Kahanem, że NaN! = NaN wywodzi się z dwóch pragmatycznych rozważań:

  • Że x == ypowinna być równoważna x - y == 0w miarę możliwości (poza bycie twierdzenie prawdziwej arytmetyki, to sprawia, że realizacja sprzętowa porównania więcej miejsca efektywny, co było niezwykle ważne w czasie średnia został opracowany - Należy jednak pamiętać, że to jest łamane dla x = y = nieskończoność, więc sam w sobie nie jest to świetny powód; mógł być rozsądnie wygięty (x - y == 0) or (x and y are both NaN)).

  • Co ważniejsze, isnan( )w chwili sformalizowania NaN w arytmetyki 8087 nie było predykatu; konieczne było zapewnienie programistom wygodnego i wydajnego sposobu wykrywania wartości NaN, który nie zależał od języków programowania, zapewniając coś takiego, isnan( )co może zająć wiele lat. Zacytuję własne pismo Kahana na ten temat:

Gdyby nie było sposobu, aby pozbyć się NaN, byłyby one tak bezużyteczne, jak Nieokreślone w CRAY; gdy tylko zostanie się napotkane, obliczenia najlepiej zatrzymać, a nie kontynuować na czas nieokreślony, aby dojść do nieokreślonego wniosku. Dlatego niektóre operacje na NaN muszą dostarczać wyniki inne niż NaN. Które operacje? … Wyjątkami są predykaty C „x == x” i „x! = X”, które odpowiednio wynoszą 1 i 0 dla każdej liczby nieskończonej lub skończonej x, ale odwrotnie, jeśli x nie jest liczbą (NaN); zapewniają one jedyne proste wyjątkowe rozróżnienie między NaN a liczbami w językach, w których brakuje słowa NaN i predykatu IsNaN (x).

Zauważ, że jest to również logika, która wyklucza zwracanie czegoś takiego jak „Nie-Boolean”. Być może ten pragmatyzm został niewłaściwie umieszczony, a standard powinien był wymagać isnan( ), ale to sprawiłoby, że NaN byłby prawie niemożliwy do wykorzystania sprawnie i wygodnie przez kilka lat, podczas gdy świat czekał na przyjęcie języka programowania. Nie jestem przekonany, że byłby to rozsądny kompromis.

Mówiąc wprost: wynik NaN == NaN nie zmieni się teraz. Lepiej nauczyć się z tym żyć, niż narzekać w Internecie. Jeśli chcesz argumentować, że relacja zamówienia odpowiednia dla kontenerów również powinna istnieć, zalecałbym zalecenie, aby twój ulubiony język programowania implementował totalOrderpredykat znormalizowany w IEEE-754 (2008). Fakt, że nie mówi jeszcze o słuszności obawy Kahana, która uzasadniała obecny stan rzeczy.

Stephen Canon
źródło
16
Przeczytałem twoje punkty 1 i 2. Następnie zauważyłem, że w prawdziwej arytmetyki (rozszerzonej, aby pozwolić NaN przede wszystkim) NaN jest sobie równa - po prostu dlatego, że w matematyce każda istota jest równa sobie, bez wyjątku. Teraz jestem zdezorientowany: dlaczego IEEE „nie pasowało do zachowania prawdziwej arytmetyki”, co spowodowałoby, że NaN == NaN? czego mi brakuje?
maks.
12
Zgoda; nierefleksyjność NaNs nie spowodowała końca bólu dla języków takich jak Python, z semantyką ograniczania opartą na równości. Ty naprawdę nie chcą równości, aby nie być relacją równoważności, gdy starasz się pojemniki budować na wierzchu. Posiadanie dwóch odrębnych pojęć równości również nie jest przyjazną opcją, ponieważ język, który powinien być łatwy do nauczenia się. Rezultat (w przypadku Pythona) jest nieprzyjemnie kruchym kompromisem między szacunkiem dla IEEE 754 a niezbyt uszkodzoną semantyką ograniczania. Na szczęście rzadko umieszcza się NaN w pojemnikach.
Mark Dickinson
5
Kilka fajnych obserwacji tutaj: bertrandmeyer.com/2010/02/06/…
Mark Dickinson
6
@StephenCanon: W jaki sposób (0/0) == (+ INF) + (-INF) byłoby bardziej nonsensowne niż posiadanie 1f/3f == 10000001f/30000002f? Jeśli wartości zmiennoprzecinkowe są uważane za klasy równoważności, a=bnie oznacza to „Obliczenia, które przyniosły ai b, gdyby zostały wykonane z nieskończoną precyzją, przyniosłyby identyczne wyniki”, ale raczej „To, co wiadomo o adopasowaniach, odpowiada temu, co wiadomo o b„. Jestem ciekawy, czy znasz jakieś przykłady kodu, w którym posiadanie „Nan! = NaN” sprawia, że ​​jest to prostsze niż byłoby w innym przypadku?
supercat
5
Teoretycznie, gdybyś miał NaN == NaN i nie ma isNaN, nadal możesz testować na NaN !(x < 0 || x == 0 || x > 0), ale byłoby to wolniejsze i bardziej niezgrabne niż x != x.
user2357112 obsługuje Monikę
50

NaN może być traktowany jako niezdefiniowany stan / liczba. podobny do pojęcia niezdefiniowanego 0/0 lub sqrt (-3) (w systemie liczb rzeczywistych, w którym żyje zmiennoprzecinkowy).

NaN jest używany jako rodzaj symbolu zastępczego dla tego niezdefiniowanego stanu. Z matematycznego punktu widzenia niezdefiniowany nie jest równy niezdefiniowanemu. Nie można również powiedzieć, że niezdefiniowana wartość jest większa lub mniejsza niż inna niezdefiniowana wartość. Dlatego wszystkie porównania zwracają wartość false.

To zachowanie jest również korzystne w przypadkach, gdy porównujesz sqrt (-3) z sqrt (-2). Oba zwrócą NaN, ale nie są równoważne, mimo że zwracają tę samą wartość. Dlatego pożądane jest zachowanie równości zawsze zwracającej fałsz, gdy mamy do czynienia z NaN.

Chris
źródło
5
Jaki powinien być wynik sqrt (1.00000000000000022) == sqrt (1.0)? Co powiesz na (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? Również tylko pięć z sześciu porównań jest fałszywych. !=Operator zwraca true. Posiadanie NaN==NaNi NaN!=NaNoba zwracają wartość false, pozwalając kodowi porównującemu x i y wybrać, co powinno się zdarzyć, gdy oba operandy są NaN, wybierając jeden ==lub !=.
supercat
38

Wrzucić jeszcze jedną analogię. Jeśli podam ci dwa pudełka i powiem ci, że żadne z nich nie zawiera jabłka, czy powiedziałbyś mi, że te pudełka zawierają to samo?

NaN nie zawiera informacji o tym, czym jest coś, tylko czym nie jest. Dlatego tych elementów nigdy nie można z całą pewnością uznać za równe.

Jack Ryan
źródło
6
Wszystkie puste zestawy są z definicji równe.
MSalters
28
Podane pola NIE są znane jako puste.
John Smith
7
Czy powiedziałbyś mi, że pudełka nie zawierają tego samego? Rozumiem uzasadnienie (NaN==Nan)==false. To, czego nie rozumiem, jest uzasadnieniem (Nan!=Nan)==true.
supercat
3
Zakładam, że NaN! = NaN jest prawdą, ponieważ x! = Y jest zdefiniowane jako! (X == y). Oczywiście nie wiem, czy specyfikacja IEEE tak to definiuje.
Kef Schecter
6
Ale w tej analogii, jeśli dałeś mi pudełko, powiedziałeś, że nie zawiera jabłek, a następnie zapytałeś mnie, czy jest równe sobie, oczekujesz, że odmówię? Ponieważ to właśnie powiedziałbym według IEEE.
średnik
12

Z artykułu w Wikipedii na temat NaN następujące praktyki mogą powodować NaN:

  • Wszystkie operacje matematyczne> z NaN jako co najmniej jednym operandem
  • Podziały 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ i -∞ / -∞
  • Mnożenie 0 × ∞ i 0 × -∞
  • Dodawanie ∞ + (-∞), (-∞) + ∞ i równoważne odejmowanie.
  • Zastosowanie funkcji do argumentów spoza jej dziedziny, w tym obliczanie pierwiastka kwadratowego liczby ujemnej, logarytm liczby ujemnej, przyjmowanie stycznej nieparzystej wielokrotności 90 stopni (lub π / 2 radianów) lub przyjmowanie odwrotnego sinusa lub cosinus liczby mniejszej niż -1 lub większej niż +1.

Ponieważ nie ma sposobu, aby dowiedzieć się, które z tych operacji stworzyły NaN, nie ma sposobu na porównanie ich, co ma sens.

Stefan Rusek
źródło
3
Co więcej, nawet gdybyś wiedział, która operacja nie pomogłaby. Potrafię skonstruować dowolną liczbę formuł, które w pewnym momencie idą do 0/0, które mają (jeśli przyjmiemy ciągłość) dobrze zdefiniowane i różne wartości w tym punkcie.
David Thornley,
4

Nie znam uzasadnienia projektowego, ale oto fragment standardu IEEE 754-1985:

„Powinna istnieć możliwość porównania liczb zmiennoprzecinkowych we wszystkich obsługiwanych formatach, nawet jeśli formaty operandów różnią się. Porównania są dokładne i nigdy nie są przepełnione ani niedopełnione. Możliwe są cztery wzajemnie wykluczające się zależności: mniejsze niż, równe, większe niż i nieuporządkowane Ostatni przypadek powstaje, gdy co najmniej jednym operandem jest NaN. Każdy NaN porównuje nieuporządkowany ze wszystkim, włączając w to siebie. ”

Rick Regan
źródło
2

Wygląda to tylko dziwnie, ponieważ większość środowisk programistycznych, które pozwalają NaN, nie obsługuje również logiki trójwartościowej. Jeśli wrzucisz do mieszanki logikę o wartości 3, staje się ona spójna:

  • (2,7 == 2,7) = prawda
  • (2,7 == 2,6) = fałsz
  • (2.7 == NaN) = nieznany
  • (NaN == NaN) = nieznany

Nawet .NET nie zapewnia bool? operator==(double v1, double v2)operatora, więc nadal masz głupi (NaN == NaN) = falsewynik.

Christian Hayter
źródło
1

Zgaduję, że NaN (nie liczba) oznacza dokładnie, że: To nie jest liczba, a zatem porównywanie jej nie ma naprawdę sensu.

To jest trochę jak arytmetyka w SQL z nulloperandami: Wszystkie dają wynik null.

Porównania liczb zmiennoprzecinkowych porównują wartości liczbowe. Dlatego nie można ich używać do wartości nienumerycznych. NaN nie można zatem porównywać w sensie numerycznym.

Daren Thomas
źródło
3
„To nie jest liczba, dlatego porównanie nie ma większego sensu”. Ciągi nie są liczbami, ale porównywanie ich ma sens.
jason
2
tak, porównywanie łańcucha do łańcucha ma sens. Ale porównywanie łańcucha do powiedzmy jabłek nie ma większego sensu. Ponieważ jabłka i gruszki nie są liczbami, czy warto je porównywać? Który jest większy?
Daren Thomas
@DarenThomas: W języku SQL ani „IF NULL = NULL THEN FOO;” ani „JEŻELI Null <> Null THEN CALL FOO;” [lub jakakolwiek jest składnia] zostanie wykonane FOO. Aby NaN był równoważny, if (NaN != NaN) foo();nie powinien zostać wykonany foo, ale tak się dzieje.
supercat,
1

Zbyt uproszczona odpowiedź jest taka, że ​​NaN nie ma wartości liczbowej, więc nie ma w tym nic do porównania z czymkolwiek innym.

Możesz rozważyć przetestowanie i zamianę swoich NaN na + INF, jeśli chcesz, aby działały jak + INF.

David R. Tribble
źródło
0

Chociaż zgadzam się, że porównania NaN z dowolną liczbą rzeczywistą powinny być nieuporządkowane, myślę, że istnieje po prostu powód, aby porównywać NaN z samym sobą. Jak na przykład odkrywa się różnicę między sygnalizowaniem NaN a cichym NaN? Jeśli uważamy sygnały za zbiór wartości boolowskich (tj. Wektor bitowy), można zapytać, czy wektory bitowe są takie same czy różne i odpowiednio uporządkować zestawy. Na przykład przy dekodowaniu maksymalnego tendencyjnego wykładnika, jeśli znacznik zostałby przesunięty w lewo, aby wyrównać najbardziej znaczący bit znaczącego na najbardziej znaczącym bicie formatu binarnego, wartość ujemna byłaby cichym NaN, a każda wartość dodatnia byłaby być sygnalizującym NaN. Zero jest oczywiście zarezerwowane na nieskończoność i porównanie byłoby nieuporządkowane. Wyrównanie MSB pozwoliłoby na bezpośrednie porównanie sygnałów nawet z różnych formatów binarnych. Dwa NaN z tym samym zestawem sygnałów byłyby zatem równoważne i nadają sens równości.

Patrick Campbell
źródło
-1

Dla mnie najprostszym sposobem na wyjaśnienie jest:

Mam coś, a jeśli to nie jest jabłko, to czy jest to pomarańcza?

Nie można porównywać NaN z czymś innym (nawet samym), ponieważ nie ma ono wartości. Może to również być dowolna wartość (oprócz liczby).

Mam coś, a jeśli nie jest równa liczbie, to czy jest to ciąg znaków?

Halil Tevfik
źródło
Co masz na myśli mówiąc „może to być dowolna wartość oprócz liczby”?
puszkin
-2

Ponieważ matematyka jest dziedziną, w której liczby „po prostu istnieją”. W informatyce musisz zainicjować te liczby i zachować ich stan zgodnie z własnymi potrzebami. W tamtych czasach inicjalizacja pamięci działała w sposób, na którym nigdy nie można było polegać. Nigdy nie możesz pozwolić sobie na myślenie o tym „och, to byłoby inicjowane przez 0xCD przez cały czas, moje algo się nie złamie” .

Potrzebujesz więc odpowiedniego nierozmieszalnego rozpuszczalnika, który jest wystarczająco lepki, aby nie dopuścić do wciągnięcia i uszkodzenia algorytmu. Dobre algorytmy obejmujące liczby będą w większości działały z relacjami, a te, jeśli relacje () zostaną pominięte.

To tylko smar, który możesz włożyć do nowej zmiennej podczas tworzenia, zamiast programować losowe piekło z pamięci komputera. A twój algorytm, cokolwiek to jest, nie złamie się.

Następnie, gdy nagle nagle dowiadujesz się, że twój algorytm wytwarza NaN, można go wyczyścić, sprawdzając każdą gałąź pojedynczo. Ponownie zasada „zawsze fałszywa” bardzo w tym pomaga.

sanaris
źródło
-4

Bardzo krótka odpowiedź:

Ponieważ następujące: nan / nan = 1 NIE wolno trzymać. W przeciwnym razie inf/infbyłoby 1.

(W związku z tym nannie może być równy nan. Jeśli chodzi o >lub <, jeśli nanprzestrzegałby jakiejkolwiek relacji zamówienia w zestawie spełniającym właściwość Archimedesa, mielibyśmy ponownie nan / nan = 1na granicy).

SeF
źródło
2
Nie, to nie ma sensu. Mamy inf = infi inf / inf = nandlatego też nan = nannie zapobiegniemy nan / nan = nan.
starblue
@starblue Masz na myśli nan / nan = 1? W każdym razie ... Twoje rozumowanie ma sens, jeśli inf i nan były takie same jak inne liczby. Tak nie jest. Powód, dla którego inf/infmusi być nan(lub nieokreślona forma w matematyce) i nie 1jest bardziej subtelny niż prosta manipulacja algebraiczna (patrz twierdzenie De L'Hospitala).
SeF