Tylko uwaga, 1 uncja zapobiegania jest lepsza niż 1 funt lekarstwa. Innymi słowy, zapobieganie wykonywaniu 0.f / 0.f jest o wiele lepsze niż sprawdzanie wstecz nanw kodzie. nanmoże być strasznie destrukcyjne dla twojego programu, jeśli pozwolisz się rozprzestrzeniać, może wprowadzić trudne do znalezienia błędy. Wynika to z tego, że nanjest toksyczny (5 * nan= nan), nannie jest niczym ( nan! = nan), nanNie większy niż nic ( nan!> 0), nanjest nie mniejszy niż cokolwiek ( nan! <0).
bobobobo,
1
@bobobobo: Jest to funkcja umożliwiająca scentralizowane sprawdzanie błędów. Podobnie jak wyjątki vs. zwracane wartości.
Ben Voigt,
2
Dlaczego <cmath> nie ma isnan ()? Jest w std ::
frankliuao
Odpowiedzi:
349
Zgodnie ze standardem IEEE wartości NaN mają dziwną właściwość polegającą na tym, że porównania z nimi związane są zawsze fałszywe. To znaczy, dla liczby zmiennoprzecinkowej f f != fbędzie prawdą tylko wtedy, gdy f jest NaN.
Zauważ, że jak zauważyły niektóre komentarze poniżej, nie wszystkie kompilatory przestrzegają tego podczas optymalizacji kodu.
Dla każdego kompilatora, który twierdzi, że używa zmiennoprzecinkowego IEEE, ta sztuczka powinna działać. Ale nie mogę zagwarantować, że to będzie działać w praktyce. W razie wątpliwości skontaktuj się ze swoim kompilatorem.
Kompilator nie powinien tego usuwać, jeśli działa w trybie IEEE. Sprawdź dokumentację swojego kompilatora, oczywiście ...
dmckee --- były kot moderator
38
-1 działa tylko w teorii, a nie w praktyce: kompilatory takie jak g ++ (z -fastmath) spieprzą to. jedynym ogólnym sposobem, do c ++ 0x, jest testowanie pod kątem bitpattern.
Pozdrawiam i hth. - Alf
66
@Alf: Dokumentacja -ffast-mathopcji wyraźnie mówi, że może to spowodować nieprawidłowe wyniki dla programów, które zależą od dokładnej implementacji, jeśli reguły / specyfikacje IEEE lub ISO dla funkcji matematycznych. Bez włączonej tej opcji używanie x != xjest całkowicie poprawnym i przenośnym sposobem testowania NaN.
Adam Rosenfield
7
@Adam: dokumentacja otwarcie stwierdza, że jest niezgodna, tak. i tak, spotkałem się już z tym argumentem, omawiając to szczegółowo z Gabrielem Dos Reis. jest powszechnie używany do obrony projektu, w okrągłym sporze (nie wiem, czy chciałeś się z tym skojarzyć, ale warto wiedzieć o tym - to jest wojna o płomienie). Twój wniosek, który x != xjest ważny bez tej opcji, nie jest logiczny. może to być prawda dla konkretnej wersji g ++ lub nie. tak czy inaczej, generalnie nie ma możliwości zagwarantowania, że opcja fastmath nie zostanie użyta.
Pozdrawiam i hth. - Alf
7
@Alf: Nie, nie byłem świadomy twojej dyskusji z Gabrielem Dos Reis. Steve Jessop doskonale podkreślił w innym pytaniu dotyczącym założenia reprezentacji IEEE. Jeśli założysz, że IEEE 754 i że kompilator działa w sposób zgodny (tj. Bez -ffast-mathopcji), to x != xjest to prawidłowe i przenośne rozwiązanie. Możesz nawet przetestować -ffast-math, testując __FAST_MATH__makro i w tym przypadku przełączyć się na inną implementację (np. Użyj unii i bitów kręconych).
Adam Rosenfield
220
W isnan()obecnej bibliotece standardowej C ++ nie jest dostępna żadna funkcja. Został wprowadzony w C99 i zdefiniowany jako makro a nie funkcja. Elementy standardowej biblioteki zdefiniowane przez C99 nie są częścią aktualnego standardu C ++ ISO / IEC 14882: 1998 ani jego aktualizacji ISO / IEC 14882: 2003.
W 2005 r. Zaproponowano sprawozdanie techniczne 1. TR1 zapewnia zgodność z C99 do C ++. Pomimo tego, że nigdy nie zostało oficjalnie przyjęte jako standard C ++, wiele ( implementacje GCC 4.0+ lub Visual C ++ 9.0+ C ++ zapewniają funkcje TR1, wszystkie lub tylko niektóre (Visual C ++ 9.0 nie zapewnia funkcji matematycznych C99) .
Jeśli TR1 jest dostępna, a następnie cmathzawiera elementy, takie jak C99 isnan(), isfinite()itp ale są one zdefiniowane jako funkcje, niewymienione makr, zazwyczaj w std::tr1::przestrzeni nazw, chociaż wiele implementacji (tj GCC 4+ w systemie Linux lub w Xcode na Mac OS X 10.5+) wstrzyknąć bezpośrednio do std::, więc std::isnanjest dobrze zdefiniowane.
Co więcej, niektóre implementacje C ++ wciąż udostępniają isnan()makro C99 dla C ++ (zawarte przez cmathlub math.h), co może powodować więcej zamieszania, a programiści mogą założyć, że jest to standardowe zachowanie.
Uwaga na temat Viusal C ++, jak wspomniano powyżej, nie zapewnia std::isnanżadnej z nich std::tr1::isnan, ale zapewnia funkcję rozszerzenia zdefiniowaną jako _isnan()dostępna od wersji Visual C ++ 6.0
Na XCode jest jeszcze więcej zabawy. Jak wspomniano, GCC 4+ definiuje std::isnan. Wydaje się, że w starszych wersjach kompilatora i biblioteki XCode (tutaj jest odpowiednia dyskusja ), nie miałem okazji się sprawdzić) zdefiniowano dwie funkcje, __inline_isnand()na Intel i __isnand()Power PC.
Każdy chce mieć takie funkcje jak isNan lub isInfinity. Dlaczego osoby odpowiedzialne nie tylko uwzględniają w swoich standardach ???? - Spróbuję dowiedzieć się, jak się dowodzić, i oddam na to głos. Poważnie.
shuhalo,
8
@shuhalo Za jeszcze?
Tomáš Zato - Przywróć Monikę
11
Ta odpowiedź powinna zostać zaktualizowana, ponieważ std::isnanjest teraz częścią standardu C ++ 11, a wsparcie zostało rozszerzone. std :: isnan został zaimplementowany w Visual Studio od Visual Studio 2013. Być może @shuhalo dowodził :-)
aberaud
170
Pierwsze rozwiązanie: jeśli używasz C ++ 11
Ponieważ o to zapytano, pojawiło się trochę nowych rozwiązań: ważne jest, aby wiedzieć, że std::isnan()jest to część C ++ 11
Pamiętaj, że jest to niezgodne z opcją -fast-matematyki, jeśli używasz g ++, zobacz inne sugestie poniżej.
Inne rozwiązania: jeśli używasz narzędzi niezgodnych z C ++ 11
W przypadku C99 w C jest to zaimplementowane jako makro, isnan(c)które zwraca wartość int. Typ xpowinien być zmiennoprzecinkowy, podwójny lub długi podwójny.
Różni dostawcy mogą, ale nie muszą zawierać funkcji isnan().
Niby przenośny sposób na sprawdzenie NaNto użycie właściwości IEEE 754, która NaNnie jest sobie równa: tj. x == xBędzie fałszywa za xbycie NaN.
Jednak ostatnia opcja może nie działać z każdym kompilatorem i niektórymi ustawieniami (szczególnie ustawieniami optymalizacji), więc w ostateczności zawsze możesz sprawdzić wzorzec bitowy ...
Zdecydowanie zasługuje na zaakceptowaną odpowiedź i zasługuje na więcej pozytywnych opinii. Dzięki za wskazówkę
LBes
3
−1std::isnan jest wciąż niezbyt dobrą rekomendacją na luty 2017, ponieważ nie działa z optymalizacją zmiennoprzecinkową g ++.
Pozdrawiam i hth. - Alf
@ Cheersandhth.-Alf: czy ta opcja jest zgodna z IEEE? Odpowiedź została zredagowana
BlueTrin
@BlueTrin: Oba x != xi isnansą wymagane do pracy na zgodność z IEEE 754. W odniesieniu do tego ostatniego standard IEEE 754-2008 stanowi, że „Implementacje powinny zapewniać następujące operacje obliczeniowe dla wszystkich obsługiwanych formatów arytmetycznych”, a „isNaN (x) jest prawdą wtedy i tylko wtedy, gdy x jest NaN”. Do sprawdzania zgodności wymagana jest ta norma is754version1985()oraz is754version2008()tam, gdzie oferuje to C ++ std::numeric_limits<Fp>::is_iec559()(ta sama norma to IEC 559). Niestety z -ffast-mathoptymalizacją, np. G ++ twierdzi zgodność, ale jest niezgodna.
Pozdrawiam i hth. - Alf
1
Ostrzeżenie: isnan (x) nie działa z opcją -ffinite-matematyki-tylko w gcc i clang
A Fog
82
W programie Boost dostępna jest także biblioteka tylko z nagłówkami, która zawiera zgrabne narzędzia do obsługi typów danych zmiennoprzecinkowych
został dodany w Boost 1.35 (właśnie odkryłem, że mój program nie kompiluje się na starej dystrybucji Linuksa).
marcin
2
jeśli skompilujesz z opcją --fast-matematyki, ta funkcja nie będzie działać zgodnie z oczekiwaniami.
Gaetano Mendola
43
Istnieją trzy „oficjalne” sposoby: isnanmakro posix , isnanszablon funkcji c ++ 0x lub wizualna _isnanfunkcja c ++ .
Niestety wykrycie, którego z nich użyć, jest raczej niepraktyczne.
I niestety nie ma niezawodnego sposobu na wykrycie, czy masz reprezentację IEEE 754 z NaN. Standardowa biblioteka oferuje taki oficjalny sposób ( numeric_limits<double>::is_iec559). Ale w praktyce kompilatory takie jak g ++ to psują.
Teoretycznie można użyć po prostu x != x, ale kompilatory takie jak g ++ i visual c ++ to psują.
W końcu przetestuj określone bity wzorcowe NaN , zakładając (i mam nadzieję, że w pewnym momencie wymuszę !) Określoną reprezentację, taką jak IEEE 754.
EDYCJA : jako przykład „kompilatorów takich jak g ++… spieprz to”, rozważ
#include<limits>#include<assert.h>void foo(double a,double b ){
assert( a != b );}int main(){typedef std::numeric_limits<double>Info;doubleconst nan1 =Info::quiet_NaN();doubleconst nan2 =Info::quiet_NaN();
foo( nan1, nan2 );}
Kompilacja z g ++ (TDM-2 mingw32) 4.4.1:
C: \ test> wpisz „C: \ Program Files \ @commands \ gnuc.bat”
@rem -finput-charset = windows-1252
@ g ++ -O -pedantic -std = c ++ 98 -Wall -Wwrite-strings% * -Wno-long-long
C: \ test> gnuc x.cpp
C: \ test> echo && działa ... || echo! nie powiodło się
Pracuje...
C: \ test> gnuc x.cpp - fast-math
C: \ test> echo && działa ... || echo! nie powiodło się
Asercja nie powiodła się: a! = B, plik x.cpp, wiersz 6
Ta aplikacja poprosiła środowisko wykonawcze o zakończenie go w nietypowy sposób.
Skontaktuj się z zespołem pomocy technicznej aplikacji, aby uzyskać więcej informacji.
! nie powiodło się
C: \ test> _
@Alf: Twój przykład działa zgodnie z oczekiwaniami dla mnie zarówno w systemie Mac OS X, jak i Linux w różnych wersjach g ++ między 4.0 a 4.5. Dokumentacja -ffast-mathopcji wyraźnie mówi, że może to spowodować nieprawidłowe wyniki dla programów, które zależą od dokładnej implementacji, jeśli reguły / specyfikacje IEEE lub ISO dla funkcji matematycznych. Bez włączonej tej opcji używanie x != xjest całkowicie poprawnym i przenośnym sposobem testowania NaN.
Adam Rosenfield
6
@Adam: Brakuje Ci tego, że standard C ++ nie wymaga reprezentacji IEEE ani matematyki dla liczb zmiennoprzecinkowych. O ile strona podręcznika mówi ci, gcc -ffast-mathwciąż jest zgodną implementacją C ++ (no cóż, zakładając, że działa numeric_limits::is_iec559poprawnie, jest, chociaż Alf sugeruje powyżej, że tak nie jest): Kod C ++ polegający na IEEE nie jest przenośnym C ++ i nie ma prawa oczekiwać implementacji, które to zapewnią.
Steve Jessop
5
I Alf ma rację, szybki test na gcc 4.3.4 i is_iec559jest to prawda -ffast-math. Problem polega na tym, że dokumenty GCC -ffast-mathmówią tylko, że nie jest to IEEE / ISO dla funkcji matematycznych, podczas gdy powinni powiedzieć, że to nie jest C ++, ponieważ jego implementacja numeric_limitsjest błędna. Domyślam się, że GCC nie zawsze może stwierdzić w momencie definiowania szablonu, czy ostateczny backend rzeczywiście ma zgodne zmiennoprzecinkowe, a więc nawet nie próbuje. IIRC istnieją podobne problemy na liście zaległych błędów zgodności G99 z C99.
Steve Jessop
1
@Alf, @Steve, nie wiedziałem, że standard C ++ nie ma specyfikacji na temat wartości zmiennoprzecinkowych. To dla mnie dość szokujące. Wygląda lepiej, obsługując IEEE 754 i NaN jako rozszerzenie specyficzne dla platformy zamiast standardowego. Czyż nie I czy mogę się spodziewać jakiegokolwiek rodzaju isnan () lub IEEE754 dodanego w C ++ 0x?
Eonil
3
@Eonil: C ++ 0x nadal ma na przykład „Reprezentacja wartości typów punktów pływających jest zdefiniowana w implementacji”. Zarówno C, jak i C ++ mają wspierać implementacje na maszynach bez sprzętu zmiennoprzecinkowego, a odpowiednie zmiennoprzecinkowe IEEE 754 mogą być nieco wolniejsze do emulacji niż rozsądnie dokładne alternatywy. Teorią jest to, że możesz twierdzić, is_iec559jeśli potrzebujesz IEEE, w praktyce nie wydaje się to działać na GCC. C ++ 0x ma jakąś isnanfunkcję, ale ponieważ GCC nie teraz poprawnie implementuje is_iec559, myślę, że nie będzie w C ++ 0x i -ffast-mathmoże ją zepsuć isnan.
Steve Jessop
39
Istnieje std :: isnan, jeśli kompilator obsługuje rozszerzenia c99, ale nie jestem pewien, czy mingw to robi.
Oto mała funkcja, która powinna działać, jeśli twój kompilator nie ma standardowej funkcji:
bool custom_isnan(double var){volatiledouble d = var;return d != d;}
Gdy to robi, kompilator zoptymalizuje porównanie, zawsze zwracając wartość true.
CTT
23
Nie, nie ma. Kompilator, który to robi, jest uszkodzony. Równie dobrze możesz powiedzieć, że istnieje szansa, że standardowa biblioteka isnanzwróci zły wynik. Technicznie prawdą jest, że kompilator może być wadliwy, ale w praktyce Not Gonna Happen. Tak samo jak var != var. Działa, ponieważ tak definiowane są wartości zmiennoprzecinkowe IEEE.
czerwiec
29
jeśli ustawiono -ffast-math, isnan () nie zwróci poprawnego wyniku dla gcc. Oczywiście, ta optymalizacja jest udokumentowana jako przełamująca semantykę IEEE ...
Matthew Herrmann
Jeśli ustawiona jest opcja -ffast-matematyka, kompilator jest uszkodzony. A raczej, jeśli ustawiona jest opcja -ffast-matematyka, wszystkie zakłady są wyłączone i i tak nie można polegać na NaN.
Adrian Ratnapala
25
Możesz użyć numeric_limits<float>::quiet_NaN( )zdefiniowanego w limitsstandardowej bibliotece do testowania. Zdefiniowano osobną stałą double.
#include<iostream>#include<math.h>#include<limits>usingnamespace std;int main(){
cout <<"The quiet NaN for type float is: "<< numeric_limits<float>::quiet_NaN()<< endl;float f_nan = numeric_limits<float>::quiet_NaN();if( isnan(f_nan)){
cout <<"Float was Not a Number: "<< f_nan << endl;}return0;}
Nie wiem, czy to działa na wszystkich platformach, ponieważ testowałem tylko z g ++ w systemie Linux.
Uważaj jednak - wydaje się, że w GCC wersja 3.2.3 występuje błąd w ograniczeniach numerycznych, ponieważ zwraca wartość 0,0 dla wartości cichej_naN. Późniejsze wersje GCC są w porządku z mojego doświadczenia.
Nathan Kitchen
@Nathan: Dobrze wiedzieć. Używam wersji 4.3.2, więc jestem poza lasem.
Bill the Lizard
18
Możesz użyć tej isnan()funkcji, ale musisz dołączyć bibliotekę matematyki C.
#include<cmath>
Ponieważ ta funkcja jest częścią C99, nie jest dostępna wszędzie. Jeśli twój dostawca nie dostarcza tej funkcji, możesz również zdefiniować własny wariant dla kompatybilności.
Używałem <cmath> i nie ma w tym isnan! przypadkowo dowiedziałem się, że tam jestisnan w <math.h>
hasen
1
Jak powiedziałem, jest to część C99. Ponieważ C99 nie jest częścią żadnego obecnego standardu C ++, podałem alternatywę. Ale ponieważ jest prawdopodobne, że isnan () zostanie włączony do nadchodzącego standardu C ++, umieściłem wokół niego dyrektywę #ifndef.
raimue
12
Poniższy kod wykorzystuje definicję NAN (zestaw wszystkich bitów wykładniczych, przynajmniej jeden zestaw bitów ułamkowych) i zakłada, że sizeof (int) = sizeof (float) = 4. Szczegółowe informacje można znaleźć w Wikipedii w NAN.
Wierzę, że działałoby to również na dużych platformach Endian. Dosłowność 0x7fffffffpo prostu pozostanie w pamięci jako ff ff ff 7f. valuema taką samą kolejność jak ma 0x7f800000, więc wszystkie operacje są w linii (nie ma zamiany bajtów). Byłbym zainteresowany, gdyby ktoś mógł to przetestować na platformie Big Endian.
Bryan W. Wagner,
0x7fff1234jest również NaN. Tak też jest0xffffffff
Steve Hollasch,
12
zapobieganie nanom
Moja odpowiedź na to pytanie nienan polega na sprawdzaniu z mocą wsteczną . Zamiast tego użyj kontroli zapobiegawczej dla podziałów formularza 0.0/0.0.
#include<float.h>float x=0.f;// I'm gonna divide by x!if(!x )// Wait! Let me check if x is 0
x = FLT_MIN ;// oh, since x was 0, i'll just make it really small instead.float y =0.f/ x ;// whew, `nan` didn't appear.
nanwynika z operacji 0.f/0.flub 0.0/0.0. nanjest straszną przeszkodą dla stabilności twojego kodu, którą trzeba bardzo ostrożnie wykryć 1 . Ich właściwości nanróżnią się od normalnych liczb:
nanjest toksyczny, (5 * nan= nan)
nannie jest niczym, nawet sam ( nan! = nan)
nannie większy niż cokolwiek ( nan!> 0)
nanjest nie mniej niż cokolwiek ( nan! <0)
Dwie ostatnie wymienione właściwości są przeciwne logice i spowodują dziwne zachowanie kodu, który opiera się na porównaniach z nanliczbą (trzecia ostatnia właściwość jest również nieparzysta, ale prawdopodobnie nigdy nie zobaczyszx != x ? w swoim kodzie (chyba że sprawdzasz dla nan (niewiarygodnie))).
W swoim własnym kodzie zauważyłem, że nanwartości często powodują błędy, które można znaleźć. (Zauważ, że tak nie jest w przypadku influb -inf. ( -inf<0) zwraca TRUE, (0 < inf) zwraca PRAWDA, a nawet ( -inf< inf) zwraca PRAWDA. Tak więc, z mojego doświadczenia, zachowanie kodu często jest nadal pożądane).
co robić pod nan
To, co chcesz się wydarzyć, 0.0/0.0musi być traktowane jako szczególny przypadek , ale to, co robisz, musi zależeć od liczb, których oczekujesz od kodu.
W powyższym przykładzie wynikiem ( 0.f/FLT_MIN) będzie w 0zasadzie. Zamiast tego możesz chcieć 0.0/0.0wygenerować HUGE. Więc,
float x=0.f, y=0.f, z;if(!x &&!y )// 0.f/0.f case
z = FLT_MAX ;// biggest float possibleelse
z = y/x ;// regular division.
Tak więc powyżej, gdyby x było 0.f,inf spowodowałoby (które ma całkiem dobre / nieniszczące zachowanie, jak wspomniano powyżej).
1 Czeki przez nanvia x != xsą czasami niewiarygodne ( x != xsą usuwane przez niektóre optymalizujące kompilatory, które łamią zgodność z IEEE, szczególnie gdy -ffast-mathprzełącznik jest włączony).
Dzięki za zwrócenie na to uwagi; takie programowanie zdecydowanie pomogłoby w rozwiązaniu problemu jako takiego. Ale następnym razem staraj się nie nadużywać funkcji formatowania tekstu. Zmiana rozmiarów czcionek, gramatury i stylu sprawia, że czytanie jest bardzo trudne.
Magnus
4
Należy zauważyć, że 0,0 / 0,0 nie jest jedyną operacją, która może spowodować powstanie NaN. Pierwiastek kwadratowy liczby ujemnej zwraca NaN. Cosinus + nieskończoności zwraca również NaN. operacja acos (x), w której x nie jest w zakresie [0, pi], może również dawać NaN. W skrócie, należy szczególnie uważać, aby spojrzeć na te potencjalnie ryzykowne operacje, nie tylko na 0,0 / 0,0.
Boris Dalstein
Całkowicie zgadzam się z Borisem. Z mojego doświadczenia wynika, że NaN praktycznie zawsze pochodzi z czegoś takiego jak sqrt (-1,302e-53), tj. Wyniki obliczeń pośrednich bliskie zera są wprowadzane do sqrt bez sprawdzania negatywności.
hans_meine
1
„Zapobieganie NaN” oznacza, że musisz dostać się do wszystkich podstawowych operacji arytmetycznych, a nie tylko podziału. Musisz uważać na ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, i wiele innych. Bycie „zapobiegawczym” przy tak podstawowych operacjach arytmetycznych oznacza, że całkowicie zatankujesz swoją wydajność (i prawdopodobnie przegapisz dodatkowe przypadki, o których nie pomyślałeś).
Steve Hollasch,
11
Począwszy od C ++ 14 istnieje wiele sposobów sprawdzenia, czy liczba zmiennoprzecinkowa valuejest NaN.
Spośród tych sposobów tylko sprawdzanie bitów reprezentacji liczby działa niezawodnie, jak zauważono w mojej pierwotnej odpowiedzi. W szczególności std::isnani często proponowana kontrola v != vnie działa niezawodnie i nie powinna być używana, aby Twój kod nie przestał działać poprawnie, gdy ktoś zdecyduje, że potrzebna jest optymalizacja zmiennoprzecinkowa, i poprosi kompilator o zrobienie tego. Ta sytuacja może się zmienić, kompilatory mogą uzyskać większą zgodność, ale w przypadku tego problemu, który nie pojawił się przez 6 lat od pierwotnej odpowiedzi.
Przez około 6 lat moją pierwotną odpowiedzią było wybrane rozwiązanie tego pytania, które było OK. Ale ostatnio wybrano bardzo pozytywną odpowiedź zalecającą niewiarygodny v != vtest. Stąd ta dodatkowa, bardziej aktualna odpowiedź (teraz mamy standardy C ++ 11 i C ++ 14 oraz C ++ 17 na horyzoncie).
Główne sposoby sprawdzania obecności NaN od C ++ 14 to:
std::isnan(value) )
jest zamierzonym standardowym sposobem biblioteki od C ++ 11. isnannajwyraźniej jest w konflikcie z makrem Posix o tej samej nazwie, ale w praktyce nie stanowi to problemu. Główny problem polega na tym, że gdy wymagana jest optymalizacja arytmetyczna zmiennoprzecinkowa, wówczas co najmniej jeden główny kompilator, mianowicie g ++, std::isnanzwraca falseargument NaN .
(fpclassify(value) == FP_NAN) )
Cierpi na ten sam problem, co std::isnannp. Nie jest wiarygodny.
(value != value) )
Zalecane w wielu odpowiedziach SO. Cierpi na ten sam problem, co std::isnannp. Nie jest wiarygodny.
(value == Fp_info::quiet_NaN()) )
Jest to test, który przy standardowym zachowaniu nie powinien wykrywać NaN, ale że przy zoptymalizowanym zachowaniu może wykryć NaN (ze względu na zoptymalizowany kod bezpośrednio porównujący reprezentacje poziomu bitów) i być może w połączeniu z innym sposobem na pokrycie standardowego niezoptymalizowanego zachowania , mógł niezawodnie wykryć NaN. Niestety okazało się, że nie działa niezawodnie.
(ilogb(value) == FP_ILOGBNAN) )
Cierpi na ten sam problem, co std::isnannp. Nie jest wiarygodny.
isunordered(1.2345, value) )
Cierpi na ten sam problem, co std::isnannp. Nie jest wiarygodny.
is_ieee754_nan( value ) )
To nie jest standardowa funkcja. Sprawdza bity zgodnie ze standardem IEEE 754. Jest całkowicie niezawodny, ale kod jest w pewnym stopniu zależny od systemu.
W poniższym kompletnym kodzie testowym „sukces” określa, czy wyrażenie zgłasza Nanowość wartości. Dla większości wyrażeń ta miara sukcesu, cel wykrycia NaN i tylko NaN, odpowiada ich standardowej semantyce. Jednak w przypadku (value == Fp_info::quiet_NaN()) )wyrażenia standardowe zachowanie polega na tym, że nie działa ono jako detektor NaN.
Wyniki z g ++ (zauważ, że standardowe zachowanie (value == Fp_info::quiet_NaN())jest takie, że nie działa jako detektor NaN, jest to po prostu bardzo praktyczne zainteresowanie tutaj):
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ --wersja | znajdź „++”
g ++ (x86_64-win32-sjlj-rev1, zbudowany przez projekt MinGW-W64) 6.3.0
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ foo.cpp && a
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 0x0100)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 0x0100)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 0x0100)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == ((int) 0x80000000))) = true Sukces
u = 3,14, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
w = inf, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ foo.cpp -ffast-math && a
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = false FAILED
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclifyify (wartość) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (wartość) == 0x0100)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 0x0100)) = false Sukces
v = nan, ((wartość! = wartość)) = false FAILED
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = true Sukces
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = true FAILED
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = true FAILED
v = nan, ((ilogb (wartość) == ((int) 0x80000000))) = true Sukces
u = 3,14, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
w = inf, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
v = nan, (isunordered (1.2345, wartość)) = false FAILED
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> _
Wyniki z Visual C ++:
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl / nologo- 2> i 1 | znajdź „++”
Microsoft (R) C / C ++ Optimization Compiler Version 19.00.23725 dla x86
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl foo.cpp / Feb && b
foo.cpp
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 2)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 2)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 2)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == 0x7fffffff)) = true Sukces
u = 3,14, ((ilogb (wartość) == 0x7fffffff)) = fałszywy sukces
w = inf, ((ilogb (wartość) == 0x7fffffff)) = true FAILED
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl foo.cpp / Feb / fp: fast && b
foo.cpp
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 2)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 2)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 2)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == 0x7fffffff)) = true Sukces
u = 3,14, ((ilogb (wartość) == 0x7fffffff)) = fałszywy sukces
w = inf, ((ilogb (wartość) == 0x7fffffff)) = true FAILED
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> _
Podsumowując powyższe wyniki, tylko bezpośrednie testowanie reprezentacji na poziomie bitów przy użyciu is_ieee754_nanfunkcji zdefiniowanej w tym programie testowym działało niezawodnie we wszystkich przypadkach zarówno z g ++, jak i Visual C ++.
Dodatek:
Po opublikowaniu powyższego dowiedziałem się o jeszcze jednym możliwym teście na NaN, wymienionym w innej odpowiedzi tutaj, mianowicie ((value < 0) == (value >= 0)). Okazało się, że działa dobrze z Visual C ++, ale zawiodło z -ffast-mathopcją g ++ . Tylko bezpośrednie testowanie bitatternów działa niezawodnie.
inlineboolIsNan(float f){constuint32 u =*(uint32*)&f;return(u&0x7F800000)==0x7F800000&&(u&0x7FFFFF);// Both NaN and qNan.}inlineboolIsNan(double d){constuint64 u =*(uint64*)&d;return(u&0x7FF0000000000000ULL)==0x7FF0000000000000ULL&&(u&0xFFFFFFFFFFFFFULL);}
Działa sizeof(int)to, jeśli ma 4 i sizeof(long long)8.
W czasie wykonywania jest to tylko porównanie, casting nie zajmuje żadnego czasu. Po prostu zmienia konfigurację flag porównania, aby sprawdzić równość.
Należy również pamiętać, że jest to ograniczone do reprezentacji IEEE 754.
Pozdrawiam i hth. - Alf
Zauważ, że ta obsada łamie ścisłą zasadę aliasingu g ++, a kompilator znany jest z wykonywania Unmentionable Things ™ po wykryciu formalnego UB. Zamiast wydajnych rzutowań, z g ++ musisz użyć memcpy, aby się upewnić, za pomocą tablicy bajtów. Kod na to w mojej odpowiedzi nr 2 .
Pozdrawiam i hth. - Alf
4
Możliwe rozwiązanie, które nie zależy od konkretnej reprezentacji IEEE dla NaN, byłoby następujące:
template<class T>bool isnan( T f ){
T _nan =(T)0.0/(T)0.0;return0== memcmp((void*)&f,(void*)&_nan,sizeof(T));}
Zmienna zmiennoprzecinkowa o pojedynczej precyzji ma ponad 8 milionów uzasadnionych i różnych reprezentacji bitów dla NaN, więc musisz dodać więcej porównań. :)
Steve Hollasch
4
Biorąc pod uwagę, że (x! = X) nie zawsze jest gwarantowane dla NaN (np. Jeśli korzystam z opcji -ffast-matematyki), używałem:
#define IS_NAN(x)(((x)<0)==((x)>=0))
Liczby nie mogą być jednocześnie <0 i> = 0, więc tak naprawdę to sprawdzenie jest pomyślne tylko wtedy, gdy liczba nie jest ani mniejsza, ani większa od lub równa zero. Który w zasadzie nie jest liczbą, ani NaN.
Możesz również użyć tego, jeśli wolisz:
#define IS_NAN(x)(!((x)<0)&&!((x)>=0)
Nie jestem jednak pewien, jak wpływa na to matematyka, więc przebieg może się różnić.
Jest to w rzeczywistości tak samo f != fwadliwe, jak i wada. Widziałem, jak lvvm optymalizuje niemal identyczny fragment kodu. Optymalizator może rozpowszechniać informacje o pierwszym porównaniu i dowiedzieć się, że drugie porównanie może nigdy nie być prawdziwe, jeśli jest to pierwsze. (jeśli kompilator ściśle przestrzega zasad IEEE i tak f != fjest znacznie prostszy)
Jeśli chodzi o mnie, rozwiązaniem może być makro, które uczyni je wyraźnie wbudowanym, a zatem wystarczająco szybkim. Działa również dla każdego typu pływaka. Opiera się na fakcie, że jedynym przypadkiem, gdy wartość nie jest równa samej sobie, jest to, że wartość nie jest liczbą.
Wydaje mi się, że najlepszym prawdziwie wieloplatformowym podejściem byłoby użycie unii i przetestowanie wzorca bitowego podwójnego w celu sprawdzenia NaN.
Nie przetestowałem dokładnie tego rozwiązania i może istnieć bardziej wydajny sposób pracy z wzorcami bitów, ale myślę, że powinien on działać.
#include<stdint.h>#include<stdio.h>unionNaN{uint64_t bits;double num;};int main(){//Test if a double is NaNdouble d =0.0/0.0;unionNaN n;
n.num = d;if((n.bits |0x800FFFFFFFFFFFFF)==0xFFFFFFFFFFFFFFFF){
printf("NaN: %f", d);}return0;}
Zauważ, że „czytanie od członka związku, który nie był ostatnio napisany, jest niezdefiniowanym zachowaniem”. Tak więc użycie unionznaku typu pun dla dwóch typów może nie działać zgodnie z oczekiwaniami (: sad_panda :). Prawidłowym (choć nie do końca tak przenośnym, jak to pożądane) byłoby całkowite uniknięcie unii i zrobienie memcpy z doubleinnej uint64_tzmiennej, a następnie wykonanie testu przy użyciu tej zmiennej pomocniczej.
Eljay
0
Na x86-64 możesz mieć niezwykle szybkie metody sprawdzania NaN i nieskończoności, które działają niezależnie od -ffast-mathopcji kompilatora. ( f != f, std::isnan, std::isinfZawsze dają falsesię -ffast-math).
Testowanie NaN, liczb nieskończoności i skończonych można łatwo wykonać, sprawdzając maksymalny wykładnik potęgi. nieskończoność to maksymalny wykładnik z zerową mantysą, NaN to maksymalny wykładnik i niezerowa mantysa. Wykładnik jest przechowywany w następnych bitach po bicie najwyższego znaku, dzięki czemu możemy po prostu w lewo przesunąć, aby pozbyć się bitu znaku i uczynić wykładnik najwyższymi bitami, nie operator&jest konieczne maskowanie ( ):
staticinlineuint64_t load_ieee754_rep(double a){uint64_t r;static_assert(sizeof r ==sizeof a,"Unexpected sizes.");
std::memcpy(&r,&a,sizeof a);// Generates movq instruction.return r;}staticinlineuint32_t load_ieee754_rep(float a){uint32_t r;static_assert(sizeof r ==sizeof a,"Unexpected sizes.");
std::memcpy(&r,&a,sizeof a);// Generates movd instruction.return r;}constexpruint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);constexpruint32_t inf_float_shl1 = UINT32_C(0xff000000);// The shift left removes the sign bit. The exponent moves into the topmost bits,// so that plain unsigned comparison is enough.staticinlinebool isnan2(double a){return load_ieee754_rep(a)<<1> inf_double_shl1;}staticinlinebool isinf2(double a){return load_ieee754_rep(a)<<1== inf_double_shl1;}staticinlinebool isfinite2(double a){return load_ieee754_rep(a)<<1< inf_double_shl1;}staticinlinebool isnan2(float a){return load_ieee754_rep(a)<<1> inf_float_shl1;}staticinlinebool isinf2(float a){return load_ieee754_rep(a)<<1== inf_float_shl1;}staticinlinebool isfinite2(float a){return load_ieee754_rep(a)<<1< inf_float_shl1;}
Te stdwersje isinfi isfiniteobciążenia 2 double/floatstałe z .datasegmentu aw najgorszym przypadku mogą one powodować 2 tęskni pamięci podręcznej danych. Powyższe wersje nie ładują żadnych danych, inf_double_shl1a inf_float_shl1stałe są kodowane jako bezpośrednie operandy w instrukcjach montażu.
Wykorzystuje fakt, że ucomisdinstrukcja ustawia flagę parzystości, jeśli dowolnym argumentem jest NaN. Tak std::isnandziała, gdy nie -ffast-mathokreślono żadnych opcji.
Standard IEEE mówi, że gdy wykładnikiem są wszystkie 1s, a mantysa nie jest równa zero, liczba jest a NaN. Double to 1bit znaku, 11bity wykładnikowe i 52bity mantysy. Sprawdź trochę.
Jak wspomniano powyżej, a! = A nie będzie działać w g ++ i niektórych innych kompilatorach, ale ta sztuczka powinna. To może nie być tak wydajne, ale wciąż jest sposób:
Zasadniczo, w g ++ (nie jestem pewien co do innych) printf wypisuje „nan” w formatach% d lub% .f, jeśli zmienna nie jest poprawną liczbą całkowitą / zmiennoprzecinkową. Dlatego ten kod sprawdza, czy pierwszym znakiem ciągu jest „n” (jak w „nan”)
Czy nie spowodowałoby to przepełnienia bufora, jeśli a = 234324.0f?
Mazyod
Tak, lub 340282346638528859811704183484516925440.000jeśli a = FLT_MAX. Będzie musiał użyć char s[7]; sprintf(s, "%.0g", a);, co będzie 6 chrs, jeśli a=-FLT_MAX, lub-3e+38
bobobobo
-3
To wykrywa nieskończoność, a także NaN w Visual Studio, sprawdzając, czy jest w podwójnych granicach:
//#include <float.h>double x, y =-1.1; x = sqrt(y);if(x >= DBL_MIN && x <= DBL_MAX )
cout <<"DETECTOR-2 of errors FAILS"<< endl;else
cout <<"DETECTOR-2 of errors OK"<< endl;
Sprawdź definicję FLT_MIN, DBL_MINa LDBL_MINbardziej dokładnie. Są to najmniejsze znormalizowane wartości dla każdego typu. Na przykład pojedyncza precyzja ma ponad 8 milionów uzasadnionych wartości denorm, które są większe od zera i mniejsze niż FLT_MIN(i nie są NaN).
nan
w kodzie.nan
może być strasznie destrukcyjne dla twojego programu, jeśli pozwolisz się rozprzestrzeniać, może wprowadzić trudne do znalezienia błędy. Wynika to z tego, żenan
jest toksyczny (5 *nan
=nan
),nan
nie jest niczym (nan
! =nan
),nan
Nie większy niż nic (nan
!> 0),nan
jest nie mniejszy niż cokolwiek (nan
! <0).Odpowiedzi:
Zgodnie ze standardem IEEE wartości NaN mają dziwną właściwość polegającą na tym, że porównania z nimi związane są zawsze fałszywe. To znaczy, dla liczby zmiennoprzecinkowej f
f != f
będzie prawdą tylko wtedy, gdy f jest NaN.Zauważ, że jak zauważyły niektóre komentarze poniżej, nie wszystkie kompilatory przestrzegają tego podczas optymalizacji kodu.
Dla każdego kompilatora, który twierdzi, że używa zmiennoprzecinkowego IEEE, ta sztuczka powinna działać. Ale nie mogę zagwarantować, że to będzie działać w praktyce. W razie wątpliwości skontaktuj się ze swoim kompilatorem.
źródło
-ffast-math
opcji wyraźnie mówi, że może to spowodować nieprawidłowe wyniki dla programów, które zależą od dokładnej implementacji, jeśli reguły / specyfikacje IEEE lub ISO dla funkcji matematycznych. Bez włączonej tej opcji używaniex != x
jest całkowicie poprawnym i przenośnym sposobem testowania NaN.x != x
jest ważny bez tej opcji, nie jest logiczny. może to być prawda dla konkretnej wersji g ++ lub nie. tak czy inaczej, generalnie nie ma możliwości zagwarantowania, że opcja fastmath nie zostanie użyta.-ffast-math
opcji), tox != x
jest to prawidłowe i przenośne rozwiązanie. Możesz nawet przetestować-ffast-math
, testując__FAST_MATH__
makro i w tym przypadku przełączyć się na inną implementację (np. Użyj unii i bitów kręconych).W
isnan()
obecnej bibliotece standardowej C ++ nie jest dostępna żadna funkcja. Został wprowadzony w C99 i zdefiniowany jako makro a nie funkcja. Elementy standardowej biblioteki zdefiniowane przez C99 nie są częścią aktualnego standardu C ++ ISO / IEC 14882: 1998 ani jego aktualizacji ISO / IEC 14882: 2003.W 2005 r. Zaproponowano sprawozdanie techniczne 1. TR1 zapewnia zgodność z C99 do C ++. Pomimo tego, że nigdy nie zostało oficjalnie przyjęte jako standard C ++, wiele ( implementacje GCC 4.0+ lub Visual C ++ 9.0+ C ++ zapewniają funkcje TR1, wszystkie lub tylko niektóre (Visual C ++ 9.0 nie zapewnia funkcji matematycznych C99) .
Jeśli TR1 jest dostępna, a następnie
cmath
zawiera elementy, takie jak C99isnan()
,isfinite()
itp ale są one zdefiniowane jako funkcje, niewymienione makr, zazwyczaj wstd::tr1::
przestrzeni nazw, chociaż wiele implementacji (tj GCC 4+ w systemie Linux lub w Xcode na Mac OS X 10.5+) wstrzyknąć bezpośrednio dostd::
, więcstd::isnan
jest dobrze zdefiniowane.Co więcej, niektóre implementacje C ++ wciąż udostępniają
isnan()
makro C99 dla C ++ (zawarte przezcmath
lubmath.h
), co może powodować więcej zamieszania, a programiści mogą założyć, że jest to standardowe zachowanie.Uwaga na temat Viusal C ++, jak wspomniano powyżej, nie zapewnia
std::isnan
żadnej z nichstd::tr1::isnan
, ale zapewnia funkcję rozszerzenia zdefiniowaną jako_isnan()
dostępna od wersji Visual C ++ 6.0Na XCode jest jeszcze więcej zabawy. Jak wspomniano, GCC 4+ definiuje
std::isnan
. Wydaje się, że w starszych wersjach kompilatora i biblioteki XCode (tutaj jest odpowiednia dyskusja ), nie miałem okazji się sprawdzić) zdefiniowano dwie funkcje,__inline_isnand()
na Intel i__isnand()
Power PC.źródło
std::isnan
jest teraz częścią standardu C ++ 11, a wsparcie zostało rozszerzone. std :: isnan został zaimplementowany w Visual Studio od Visual Studio 2013. Być może @shuhalo dowodził :-)Pierwsze rozwiązanie: jeśli używasz C ++ 11
Ponieważ o to zapytano, pojawiło się trochę nowych rozwiązań: ważne jest, aby wiedzieć, że
std::isnan()
jest to część C ++ 11Streszczenie
Zdefiniowane w nagłówku
<cmath>
Określa, czy podana liczba zmiennoprzecinkowa arg nie jest liczbą (
NaN
).Parametry
arg
: wartość zmiennoprzecinkowaZwracana wartość
true
jeśli arg jestNaN
, wfalse
przeciwnym razieOdniesienie
http://en.cppreference.com/w/cpp/numeric/math/isnan
Pamiętaj, że jest to niezgodne z opcją -fast-matematyki, jeśli używasz g ++, zobacz inne sugestie poniżej.
Inne rozwiązania: jeśli używasz narzędzi niezgodnych z C ++ 11
W przypadku C99 w C jest to zaimplementowane jako makro,
isnan(c)
które zwraca wartość int. Typx
powinien być zmiennoprzecinkowy, podwójny lub długi podwójny.Różni dostawcy mogą, ale nie muszą zawierać funkcji
isnan()
.Niby przenośny sposób na sprawdzenie
NaN
to użycie właściwości IEEE 754, któraNaN
nie jest sobie równa: tj.x == x
Będzie fałszywa zax
bycieNaN
.Jednak ostatnia opcja może nie działać z każdym kompilatorem i niektórymi ustawieniami (szczególnie ustawieniami optymalizacji), więc w ostateczności zawsze możesz sprawdzić wzorzec bitowy ...
źródło
std::isnan
jest wciąż niezbyt dobrą rekomendacją na luty 2017, ponieważ nie działa z optymalizacją zmiennoprzecinkową g ++.x != x
iisnan
są wymagane do pracy na zgodność z IEEE 754. W odniesieniu do tego ostatniego standard IEEE 754-2008 stanowi, że „Implementacje powinny zapewniać następujące operacje obliczeniowe dla wszystkich obsługiwanych formatów arytmetycznych”, a „isNaN (x) jest prawdą wtedy i tylko wtedy, gdy x jest NaN”. Do sprawdzania zgodności wymagana jest ta normais754version1985()
orazis754version2008()
tam, gdzie oferuje to C ++std::numeric_limits<Fp>::is_iec559()
(ta sama norma to IEC 559). Niestety z-ffast-math
optymalizacją, np. G ++ twierdzi zgodność, ale jest niezgodna.W programie Boost dostępna jest także biblioteka tylko z nagłówkami, która zawiera zgrabne narzędzia do obsługi typów danych zmiennoprzecinkowych
Otrzymasz następujące funkcje:
Jeśli masz czas, zapoznaj się z całym zestawem narzędzi matematycznych z Boost, ma wiele przydatnych narzędzi i szybko rośnie.
Również w przypadku punktów zmiennoprzecinkowych i nieprzemiennych dobrym pomysłem może być przyjrzenie się konwersji liczbowej .
źródło
Istnieją trzy „oficjalne” sposoby:
isnan
makro posix ,isnan
szablon funkcji c ++ 0x lub wizualna_isnan
funkcja c ++ .Niestety wykrycie, którego z nich użyć, jest raczej niepraktyczne.
I niestety nie ma niezawodnego sposobu na wykrycie, czy masz reprezentację IEEE 754 z NaN. Standardowa biblioteka oferuje taki oficjalny sposób (
numeric_limits<double>::is_iec559
). Ale w praktyce kompilatory takie jak g ++ to psują.Teoretycznie można użyć po prostu
x != x
, ale kompilatory takie jak g ++ i visual c ++ to psują.W końcu przetestuj określone bity wzorcowe NaN , zakładając (i mam nadzieję, że w pewnym momencie wymuszę !) Określoną reprezentację, taką jak IEEE 754.
EDYCJA : jako przykład „kompilatorów takich jak g ++… spieprz to”, rozważ
Kompilacja z g ++ (TDM-2 mingw32) 4.4.1:
źródło
-ffast-math
opcji wyraźnie mówi, że może to spowodować nieprawidłowe wyniki dla programów, które zależą od dokładnej implementacji, jeśli reguły / specyfikacje IEEE lub ISO dla funkcji matematycznych. Bez włączonej tej opcji używaniex != x
jest całkowicie poprawnym i przenośnym sposobem testowania NaN.gcc -ffast-math
wciąż jest zgodną implementacją C ++ (no cóż, zakładając, że działanumeric_limits::is_iec559
poprawnie, jest, chociaż Alf sugeruje powyżej, że tak nie jest): Kod C ++ polegający na IEEE nie jest przenośnym C ++ i nie ma prawa oczekiwać implementacji, które to zapewnią.is_iec559
jest to prawda-ffast-math
. Problem polega na tym, że dokumenty GCC-ffast-math
mówią tylko, że nie jest to IEEE / ISO dla funkcji matematycznych, podczas gdy powinni powiedzieć, że to nie jest C ++, ponieważ jego implementacjanumeric_limits
jest błędna. Domyślam się, że GCC nie zawsze może stwierdzić w momencie definiowania szablonu, czy ostateczny backend rzeczywiście ma zgodne zmiennoprzecinkowe, a więc nawet nie próbuje. IIRC istnieją podobne problemy na liście zaległych błędów zgodności G99 z C99.is_iec559
jeśli potrzebujesz IEEE, w praktyce nie wydaje się to działać na GCC. C ++ 0x ma jakąśisnan
funkcję, ale ponieważ GCC nie teraz poprawnie implementujeis_iec559
, myślę, że nie będzie w C ++ 0x i-ffast-math
może ją zepsućisnan
.Istnieje std :: isnan, jeśli kompilator obsługuje rozszerzenia c99, ale nie jestem pewien, czy mingw to robi.
Oto mała funkcja, która powinna działać, jeśli twój kompilator nie ma standardowej funkcji:
źródło
isnan
zwróci zły wynik. Technicznie prawdą jest, że kompilator może być wadliwy, ale w praktyce Not Gonna Happen. Tak samo jakvar != var
. Działa, ponieważ tak definiowane są wartości zmiennoprzecinkowe IEEE.Możesz użyć
numeric_limits<float>::quiet_NaN( )
zdefiniowanego wlimits
standardowej bibliotece do testowania. Zdefiniowano osobną stałądouble
.Nie wiem, czy to działa na wszystkich platformach, ponieważ testowałem tylko z g ++ w systemie Linux.
źródło
Możesz użyć tej
isnan()
funkcji, ale musisz dołączyć bibliotekę matematyki C.Ponieważ ta funkcja jest częścią C99, nie jest dostępna wszędzie. Jeśli twój dostawca nie dostarcza tej funkcji, możesz również zdefiniować własny wariant dla kompatybilności.
źródło
isnan
w <math.h>Poniższy kod wykorzystuje definicję NAN (zestaw wszystkich bitów wykładniczych, przynajmniej jeden zestaw bitów ułamkowych) i zakłada, że sizeof (int) = sizeof (float) = 4. Szczegółowe informacje można znaleźć w Wikipedii w NAN.
bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }
źródło
0x7fffffff
po prostu pozostanie w pamięci jakoff ff ff 7f
.value
ma taką samą kolejność jak ma0x7f800000
, więc wszystkie operacje są w linii (nie ma zamiany bajtów). Byłbym zainteresowany, gdyby ktoś mógł to przetestować na platformie Big Endian.0x7fff1234
jest również NaN. Tak też jest0xffffffff
zapobieganie nanom
Moja odpowiedź na to pytanie nie
nan
polega na sprawdzaniu z mocą wsteczną . Zamiast tego użyj kontroli zapobiegawczej dla podziałów formularza0.0/0.0
.nan
wynika z operacji0.f/0.f
lub0.0/0.0
.nan
jest straszną przeszkodą dla stabilności twojego kodu, którą trzeba bardzo ostrożnie wykryć 1 . Ich właściwościnan
różnią się od normalnych liczb:nan
jest toksyczny, (5 *nan
=nan
)nan
nie jest niczym, nawet sam (nan
! =nan
)nan
nie większy niż cokolwiek (nan
!> 0)nan
jest nie mniej niż cokolwiek (nan
! <0)Dwie ostatnie wymienione właściwości są przeciwne logice i spowodują dziwne zachowanie kodu, który opiera się na porównaniach z
nan
liczbą (trzecia ostatnia właściwość jest również nieparzysta, ale prawdopodobnie nigdy nie zobaczyszx != x ?
w swoim kodzie (chyba że sprawdzasz dla nan (niewiarygodnie))).W swoim własnym kodzie zauważyłem, że
nan
wartości często powodują błędy, które można znaleźć. (Zauważ, że tak nie jest w przypadkuinf
lub-inf
. (-inf
<0) zwracaTRUE
, (0 <inf
) zwraca PRAWDA, a nawet (-inf
<inf
) zwraca PRAWDA. Tak więc, z mojego doświadczenia, zachowanie kodu często jest nadal pożądane).co robić pod nan
To, co chcesz się wydarzyć,
0.0/0.0
musi być traktowane jako szczególny przypadek , ale to, co robisz, musi zależeć od liczb, których oczekujesz od kodu.W powyższym przykładzie wynikiem (
0.f/FLT_MIN
) będzie w0
zasadzie. Zamiast tego możesz chcieć0.0/0.0
wygenerowaćHUGE
. Więc,Tak więc powyżej, gdyby x było
0.f
,inf
spowodowałoby (które ma całkiem dobre / nieniszczące zachowanie, jak wspomniano powyżej).Pamiętaj, dzielenie liczb całkowitych przez 0 powoduje wyjątek czasu wykonywania . Dlatego zawsze musisz sprawdzać dzielenie liczb całkowitych przez 0. To, że
0.0/0.0
cicho ocenianan
, nie oznacza, że możesz być leniwy i nie sprawdzać,0.0/0.0
zanim to nastąpi.1 Czeki przez
nan
viax != x
są czasami niewiarygodne (x != x
są usuwane przez niektóre optymalizujące kompilatory, które łamią zgodność z IEEE, szczególnie gdy-ffast-math
przełącznik jest włączony).źródło
Począwszy od C ++ 14 istnieje wiele sposobów sprawdzenia, czy liczba zmiennoprzecinkowa
value
jest NaN.Spośród tych sposobów tylko sprawdzanie bitów reprezentacji liczby działa niezawodnie, jak zauważono w mojej pierwotnej odpowiedzi. W szczególności
std::isnan
i często proponowana kontrolav != v
nie działa niezawodnie i nie powinna być używana, aby Twój kod nie przestał działać poprawnie, gdy ktoś zdecyduje, że potrzebna jest optymalizacja zmiennoprzecinkowa, i poprosi kompilator o zrobienie tego. Ta sytuacja może się zmienić, kompilatory mogą uzyskać większą zgodność, ale w przypadku tego problemu, który nie pojawił się przez 6 lat od pierwotnej odpowiedzi.Przez około 6 lat moją pierwotną odpowiedzią było wybrane rozwiązanie tego pytania, które było OK. Ale ostatnio wybrano bardzo pozytywną odpowiedź zalecającą niewiarygodny
v != v
test. Stąd ta dodatkowa, bardziej aktualna odpowiedź (teraz mamy standardy C ++ 11 i C ++ 14 oraz C ++ 17 na horyzoncie).Główne sposoby sprawdzania obecności NaN od C ++ 14 to:
std::isnan(value) )
jest zamierzonym standardowym sposobem biblioteki od C ++ 11.
isnan
najwyraźniej jest w konflikcie z makrem Posix o tej samej nazwie, ale w praktyce nie stanowi to problemu. Główny problem polega na tym, że gdy wymagana jest optymalizacja arytmetyczna zmiennoprzecinkowa, wówczas co najmniej jeden główny kompilator, mianowicie g ++,std::isnan
zwracafalse
argument NaN .(fpclassify(value) == FP_NAN) )
Cierpi na ten sam problem, co
std::isnan
np. Nie jest wiarygodny.(value != value) )
Zalecane w wielu odpowiedziach SO. Cierpi na ten sam problem, co
std::isnan
np. Nie jest wiarygodny.(value == Fp_info::quiet_NaN()) )
Jest to test, który przy standardowym zachowaniu nie powinien wykrywać NaN, ale że przy zoptymalizowanym zachowaniu może wykryć NaN (ze względu na zoptymalizowany kod bezpośrednio porównujący reprezentacje poziomu bitów) i być może w połączeniu z innym sposobem na pokrycie standardowego niezoptymalizowanego zachowania , mógł niezawodnie wykryć NaN. Niestety okazało się, że nie działa niezawodnie.
(ilogb(value) == FP_ILOGBNAN) )
Cierpi na ten sam problem, co
std::isnan
np. Nie jest wiarygodny.isunordered(1.2345, value) )
Cierpi na ten sam problem, co
std::isnan
np. Nie jest wiarygodny.is_ieee754_nan( value ) )
To nie jest standardowa funkcja. Sprawdza bity zgodnie ze standardem IEEE 754. Jest całkowicie niezawodny, ale kod jest w pewnym stopniu zależny od systemu.
W poniższym kompletnym kodzie testowym „sukces” określa, czy wyrażenie zgłasza Nanowość wartości. Dla większości wyrażeń ta miara sukcesu, cel wykrycia NaN i tylko NaN, odpowiada ich standardowej semantyce. Jednak w przypadku
(value == Fp_info::quiet_NaN()) )
wyrażenia standardowe zachowanie polega na tym, że nie działa ono jako detektor NaN.Wyniki z g ++ (zauważ, że standardowe zachowanie
(value == Fp_info::quiet_NaN())
jest takie, że nie działa jako detektor NaN, jest to po prostu bardzo praktyczne zainteresowanie tutaj):Wyniki z Visual C ++:
Podsumowując powyższe wyniki, tylko bezpośrednie testowanie reprezentacji na poziomie bitów przy użyciu
is_ieee754_nan
funkcji zdefiniowanej w tym programie testowym działało niezawodnie we wszystkich przypadkach zarówno z g ++, jak i Visual C ++.Dodatek:
Po opublikowaniu powyższego dowiedziałem się o jeszcze jednym możliwym teście na NaN, wymienionym w innej odpowiedzi tutaj, mianowicie
((value < 0) == (value >= 0))
. Okazało się, że działa dobrze z Visual C ++, ale zawiodło z-ffast-math
opcją g ++ . Tylko bezpośrednie testowanie bitatternów działa niezawodnie.źródło
Działa
sizeof(int)
to, jeśli ma 4 isizeof(long long)
8.W czasie wykonywania jest to tylko porównanie, casting nie zajmuje żadnego czasu. Po prostu zmienia konfigurację flag porównania, aby sprawdzić równość.
źródło
memcpy
, aby się upewnić, za pomocą tablicy bajtów. Kod na to w mojej odpowiedzi nr 2 .Możliwe rozwiązanie, które nie zależy od konkretnej reprezentacji IEEE dla NaN, byłoby następujące:
źródło
Biorąc pod uwagę, że (x! = X) nie zawsze jest gwarantowane dla NaN (np. Jeśli korzystam z opcji -ffast-matematyki), używałem:
Liczby nie mogą być jednocześnie <0 i> = 0, więc tak naprawdę to sprawdzenie jest pomyślne tylko wtedy, gdy liczba nie jest ani mniejsza, ani większa od lub równa zero. Który w zasadzie nie jest liczbą, ani NaN.
Możesz również użyć tego, jeśli wolisz:
Nie jestem jednak pewien, jak wpływa na to matematyka, więc przebieg może się różnić.
źródło
f != f
wadliwe, jak i wada. Widziałem, jak lvvm optymalizuje niemal identyczny fragment kodu. Optymalizator może rozpowszechniać informacje o pierwszym porównaniu i dowiedzieć się, że drugie porównanie może nigdy nie być prawdziwe, jeśli jest to pierwsze. (jeśli kompilator ściśle przestrzega zasad IEEE i takf != f
jest znacznie prostszy)-ffast-math
opcją g ++ . Działa z Visual C ++. Zobacz ( stackoverflow.com/a/42138465/464581 ).Jeśli chodzi o mnie, rozwiązaniem może być makro, które uczyni je wyraźnie wbudowanym, a zatem wystarczająco szybkim. Działa również dla każdego typu pływaka. Opiera się na fakcie, że jedynym przypadkiem, gdy wartość nie jest równa samej sobie, jest to, że wartość nie jest liczbą.
źródło
To działa:
wyjście: isnan
źródło
Wydaje mi się, że najlepszym prawdziwie wieloplatformowym podejściem byłoby użycie unii i przetestowanie wzorca bitowego podwójnego w celu sprawdzenia NaN.
Nie przetestowałem dokładnie tego rozwiązania i może istnieć bardziej wydajny sposób pracy z wzorcami bitów, ale myślę, że powinien on działać.
źródło
union
znaku typu pun dla dwóch typów może nie działać zgodnie z oczekiwaniami (: sad_panda :). Prawidłowym (choć nie do końca tak przenośnym, jak to pożądane) byłoby całkowite uniknięcie unii i zrobienie memcpy zdouble
innejuint64_t
zmiennej, a następnie wykonanie testu przy użyciu tej zmiennej pomocniczej.Na x86-64 możesz mieć niezwykle szybkie metody sprawdzania NaN i nieskończoności, które działają niezależnie od
-ffast-math
opcji kompilatora. (f != f
,std::isnan
,std::isinf
Zawsze dająfalse
się-ffast-math
).Testowanie NaN, liczb nieskończoności i skończonych można łatwo wykonać, sprawdzając maksymalny wykładnik potęgi. nieskończoność to maksymalny wykładnik z zerową mantysą, NaN to maksymalny wykładnik i niezerowa mantysa. Wykładnik jest przechowywany w następnych bitach po bicie najwyższego znaku, dzięki czemu możemy po prostu w lewo przesunąć, aby pozbyć się bitu znaku i uczynić wykładnik najwyższymi bitami, nie
operator&
jest konieczne maskowanie ( ):Te
std
wersjeisinf
iisfinite
obciążenia 2double/float
stałe z.data
segmentu aw najgorszym przypadku mogą one powodować 2 tęskni pamięci podręcznej danych. Powyższe wersje nie ładują żadnych danych,inf_double_shl1
ainf_float_shl1
stałe są kodowane jako bezpośrednie operandy w instrukcjach montażu.Szybsze
isnan2
są tylko 2 instrukcje montażu:Wykorzystuje fakt, że
ucomisd
instrukcja ustawia flagę parzystości, jeśli dowolnym argumentem jest NaN. Takstd::isnan
działa, gdy nie-ffast-math
określono żadnych opcji.źródło
Standard IEEE mówi, że gdy wykładnikiem są wszystkie
1
s, a mantysa nie jest równa zero, liczba jest aNaN
. Double to1
bit znaku,11
bity wykładnikowe i52
bity mantysy. Sprawdź trochę.źródło
Jak wspomniano powyżej, a! = A nie będzie działać w g ++ i niektórych innych kompilatorach, ale ta sztuczka powinna. To może nie być tak wydajne, ale wciąż jest sposób:
Zasadniczo, w g ++ (nie jestem pewien co do innych) printf wypisuje „nan” w formatach% d lub% .f, jeśli zmienna nie jest poprawną liczbą całkowitą / zmiennoprzecinkową. Dlatego ten kod sprawdza, czy pierwszym znakiem ciągu jest „n” (jak w „nan”)
źródło
340282346638528859811704183484516925440.000
jeśli a =FLT_MAX
. Będzie musiał użyćchar s[7]; sprintf(s, "%.0g", a);
, co będzie 6 chrs, jeślia=-FLT_MAX
, lub-3e+38
To wykrywa nieskończoność, a także NaN w Visual Studio, sprawdzając, czy jest w podwójnych granicach:
źródło
FLT_MIN
,DBL_MIN
aLDBL_MIN
bardziej dokładnie. Są to najmniejsze znormalizowane wartości dla każdego typu. Na przykład pojedyncza precyzja ma ponad 8 milionów uzasadnionych wartości denorm, które są większe od zera i mniejsze niżFLT_MIN
(i nie są NaN).