Vérifier si un double (ou flottant) est NaN en C ++

369

Existe-t-il une fonction isnan ()?

PS: je suis à MinGW (si cela fait une différence).

J'ai résolu ce problème en utilisant isnan () de <math.h>, qui n'existe pas, dans <cmath>lequel j'étais #includeau début.

hasen
la source
2
Je ne suis pas pur, vous pouvez le faire de manière portable. Qui a dit que C ++ nécessite IEEE754?
David Heffernan
Juste une note, 1 once de prévention vaut mieux que 1 livre de cure. En d'autres termes, empêcher l'exécution de 0.f / 0.f est bien mieux que de vérifier rétroactivement nanles s dans votre code. nanCela peut être terriblement destructeur pour votre programme, s'il est autorisé à proliférer, il peut introduire des bogues difficiles à trouver. En effet, nanest toxique, (5 * nan= nan), nann'est égal à rien ( nan! = nan), nanNi supérieur à rien ( nan!> 0), nanni inférieur à rien ( nan! <0).
bobobobo
1
@bobobobo: c'est une fonctionnalité qui permet une vérification centralisée des erreurs. Tout comme les exceptions par rapport aux valeurs de retour.
Ben Voigt
2
Pourquoi <cmath> n'a-t-il pas isnan ()? C'est en std ::
frankliuao

Réponses:

351

Selon la norme IEEE, les valeurs NaN ont la propriété étrange que les comparaisons les impliquant sont toujours fausses. Autrement dit, pour un flottant f, f != fne sera vrai que si f est NaN.

Notez que, comme certains commentaires ci-dessous l'ont souligné, tous les compilateurs ne respectent pas cela lors de l'optimisation du code.

Pour tout compilateur qui prétend utiliser la virgule flottante IEEE, cette astuce devrait fonctionner. Mais je ne peux pas garantir que cela va fonctionner dans la pratique. Vérifiez auprès de votre compilateur en cas de doute.

jalf
la source
4
Le compilateur ferait mieux de ne pas supprimer cela s'il fonctionne en mode IEEE. Consultez la documentation de votre compilateur, bien sûr ...
dmckee --- chaton ex-modérateur
38
-1 ne fonctionne qu'en théorie, pas en pratique: des compilateurs comme g ++ (avec -fastmath) bousillent ça. la seule façon générale, jusqu'à c ++ 0x, est de tester le bitpattern.
Bravo et hth. - Alf
66
@Alf: La documentation de l' -ffast-mathoption indique explicitement qu'elle peut entraîner une sortie incorrecte pour les programmes qui dépendent d'une implémentation exacte si les règles / spécifications IEEE ou ISO pour les fonctions mathématiques. Sans cette option activée, l'utilisation x != xest un moyen parfaitement valide et portable de tester NaN.
Adam Rosenfield
7
@Adam: la documentation indique ouvertement qu'elle n'est pas conforme, oui. et oui, j'ai déjà rencontré cet argument, en discutant longuement avec Gabriel Dos Reis. il est couramment utilisé pour défendre le design, dans un argument circulaire (je ne sais pas si vous aviez l'intention de vous y associer, mais il vaut la peine de le savoir - ce sont des trucs de guerre des flammes). votre conclusion x != xvalable sans cette option ne suit pas logiquement. cela peut être vrai pour une version particulière de g ++, ou non. de toute façon, vous n'avez généralement aucun moyen de garantir que l'option fastmath ne sera pas utilisée.
Bravo et hth. - Alf
7
@Alf: Non, je n'étais pas au courant de votre discussion avec Gabriel Dos Reis. Steve Jessop a fait un grand point dans l'autre question sur la prise en charge de la représentation IEEE. Si vous supposez IEEE 754 et que le compilateur fonctionne de manière conforme (c'est-à-dire sans l' -ffast-mathoption), alors x != xc'est une solution valide et portable. Vous pouvez même tester -ffast-mathen testant la __FAST_MATH__macro et basculer vers une implémentation différente dans ce cas (par exemple, utiliser les unions et le twiddling de bits).
Adam Rosenfield
220

Aucune isnan()fonction n'est disponible dans la bibliothèque standard C ++ actuelle. Il a été introduit en C99 et défini comme une macro et non comme une fonction. Les éléments de la bibliothèque standard définie par C99 ne font pas partie de la norme C ++ ISO / IEC 14882: 1998 actuelle ni de sa mise à jour ISO / IEC 14882: 2003.

En 2005, le rapport technique 1 a été proposé. Le TR1 apporte la compatibilité avec C99 à C ++. En dépit du fait qu'il n'a jamais été officiellement adopté pour devenir la norme C ++, beaucoup ( les implémentations GCC 4.0+ ou Visual C ++ 9.0+ C ++ fournissent des fonctionnalités TR1, toutes ou seulement certaines (Visual C ++ 9.0 ne fournit pas de fonctions mathématiques C99) .

Si TR1 est disponible, cmathcomprend des éléments tels que C99 isnan(), isfinite()etc. , mais ils sont définis comme des fonctions, pas de macros, généralement dans l' std::tr1::espace de noms, bien que de nombreuses implémentations (ie gcc 4+ sous Linux ou dans Xcode sous Mac OS X 10.5+) Injecter les directement à std::, std::isnanest donc bien défini.

De plus, certaines implémentations de C ++ rendent toujours la isnan()macro C99 disponible pour C ++ (incluse via cmathou math.h), ce qui peut provoquer plus de confusions et les développeurs peuvent supposer que c'est un comportement standard.

Une note sur Viusal C ++, comme mentionné ci-dessus, il ne fournit pas non std::isnanplus std::tr1::isnan, mais il fournit une fonction d'extension définie comme _isnan()disponible depuis Visual C ++ 6.0

Sur XCode, il y a encore plus de plaisir. Comme mentionné, GCC 4+ définit std::isnan. Pour les versions plus anciennes du compilateur et de la bibliothèque sous forme de XCode, il semble (voici une discussion pertinente ) que je n'ai pas eu la chance de me vérifier) ​​deux fonctions sont définies, __inline_isnand()sur Intel et __isnand()sur Power PC.

mloskot
la source
21
Tout le monde veut ces fonctions comme isNan ou isInfinity. Pourquoi les responsables n'incluent-ils pas simplement dans leurs normes ???? - Je vais essayer de savoir comment me prendre en main et voter pour cela. Sérieusement.
shuhalo
8
@shuhalo Encore en charge?
Tomáš Zato - Reinstate Monica
11
Cette réponse devrait être mise à jour car elle std::isnanfait désormais partie de la norme C ++ 11 et le support s'est étendu. std :: isnan a été implémenté dans Visual Studio à partir de Visual Studio 2013. Peut-être que @shuhalo a pris en charge :-)
aberaud
170

Première solution: si vous utilisez C ++ 11

Depuis que cela a été demandé, il y a eu un peu de nouveaux développements: il est important de savoir que cela std::isnan()fait partie de C ++ 11

Synopsis

Défini dans l'en-tête <cmath>

bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)

Détermine si le nombre à virgule flottante donné arg n'est pas un nombre ( NaN).

Paramètres

arg: valeur en virgule flottante

Valeur de retour

truesi arg l'est NaN, falsesinon

Référence

http://en.cppreference.com/w/cpp/numeric/math/isnan

Veuillez noter que ceci est incompatible avec -fast-math si vous utilisez g ++, voir ci-dessous pour d'autres suggestions.


Autres solutions: si vous utilisez des outils non conformes à C ++ 11

Pour C99, en C, cela est implémenté comme une macro isnan(c)qui renvoie une valeur int. Le type de xdoit être float, double ou long double.

Divers fournisseurs peuvent ou non inclure ou non une fonction isnan().

La façon soi - disant portable pour vérifier NaNest d'utiliser la propriété IEEE 754 qui NaNne correspond pas à lui - même: à savoir x == xsera faux pour xêtre NaN.

Cependant, la dernière option peut ne pas fonctionner avec tous les compilateurs et certains paramètres (en particulier les paramètres d'optimisation), donc en dernier recours, vous pouvez toujours vérifier le modèle de bits ...

BlueTrin
la source
8
Mérite certainement d'être la réponse acceptée et mérite plus de votes positifs. Merci pour l'astuce
LBes
3
−1 std::isnan est toujours une mauvaise recommandation en février 2017, car il ne fonctionne pas avec l'optimisation à virgule flottante de g ++.
Bravo et hth. - Alf
@ Cheersandhth.-Alf: cette option est-elle compatible IEEE? La réponse a été modifiée
BlueTrin
@BlueTrin: Les deux x != xet isnandoivent fonctionner pour la conformité IEEE 754. Concernant ce dernier, la norme IEEE 754-2008 stipule que «les implémentations doivent fournir les opérations non informatiques suivantes pour tous les formats arithmétiques pris en charge» et «isNaN (x) est vrai si et seulement si x est un NaN». Pour vérifier la conformité, cette norme exige is754version1985()et is754version2008(), où C ++ offre à la place std::numeric_limits<Fp>::is_iec559()(CEI 559 est la même norme). Malheureusement avec l' -ffast-mathoptimisation, par exemple, g ++ revendique la conformité mais n'est pas conforme.
Bravo et hth. - Alf
1
Avertissement: isnan (x) ne fonctionne pas avec l'option -ffinite-math-only dans gcc et clang
A Fog
82

Il y a aussi une bibliothèque uniquement en-tête présente dans Boost qui a des outils soignés pour gérer les types de données à virgule flottante

#include <boost/math/special_functions/fpclassify.hpp>

Vous obtenez les fonctions suivantes:

template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);

Si vous avez le temps, jetez un œil à la boîte à outils Math complète de Boost, elle contient de nombreux outils utiles et se développe rapidement.

De plus, lorsqu'il s'agit de points flottants et non flottants, il peut être judicieux d'examiner les conversions numériques .

Anonyme
la source
1
Merci! Exactement ce que je cherchais.
Dr Watson
il a été ajouté dans Boost 1.35 (je viens de découvrir que mon programme ne compile pas sur l'ancienne distribution Linux).
marcin
2
si vous compilez avec l'option --fast-math alors cette fonction ne fonctionnera pas comme prévu.
Gaetano Mendola
43

Il y a trois façons "officielles": posix isnanmacro , c ++ 0x isnanmodèle de fonction , ou c ++ visuelle _isnanfonction .

Malheureusement, il est plutôt difficile de détecter lequel utiliser.

Et malheureusement, il n'existe aucun moyen fiable de détecter si vous avez une représentation IEEE 754 avec NaN. La bibliothèque standard propose une telle manière officielle ( numeric_limits<double>::is_iec559). Mais dans la pratique, les compilateurs tels que g ++ bousillent ça.

En théorie, on pourrait utiliser simplement x != x, mais des compilateurs tels que g ++ et visual c ++ bousillent ça.

Donc, à la fin, testez les modèles de bits NaN spécifiques , en supposant (et, espérons-le, en appliquant, à un moment donné!) Une représentation particulière telle que IEEE 754.


EDIT : comme exemple de "compilateurs tels que g ++… bousiller ça", considérons

#include <limits>
#include <assert.h>

void foo( double a, double b )
{
    assert( a != b );
}

int main()
{
    typedef std::numeric_limits<double> Info;
    double const nan1 = Info::quiet_NaN();
    double const nan2 = Info::quiet_NaN();
    foo( nan1, nan2 );
}

Compilation avec g ++ (TDM-2 mingw32) 4.4.1:

C: \ test> tapez "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> un && écho fonctionne ... || écho! échoué
travaux...

C: \ test> gnuc x.cpp --fast-math

C: \ test> un && écho fonctionne ... || écho! échoué
Échec de l'assertion: a! = B, fichier x.cpp, ligne 6

Cette application a demandé au Runtime de la terminer de manière inhabituelle.
Veuillez contacter l'équipe d'assistance de l'application pour plus d'informations.
!échoué

C: \ test> _
Santé et hth. - Alf
la source
4
@Alf: Votre exemple fonctionne comme prévu pour moi sur Mac OS X et Linux sur différentes versions de g ++ entre 4.0 et 4.5. La documentation de l' -ffast-mathoption indique explicitement qu'elle peut entraîner une sortie incorrecte pour les programmes qui dépendent d'une implémentation exacte si les règles / spécifications IEEE ou ISO pour les fonctions mathématiques. Sans cette option activée, l'utilisation x != xest un moyen parfaitement valide et portable de tester NaN.
Adam Rosenfield
6
@Adam: Ce qui vous manque, c'est que la norme C ++ ne nécessite pas de représentation IEEE ou de mathématiques pour les flottants. Pour autant que la page de manuel vous l'indique, il gcc -ffast-maths'agit toujours d'une implémentation C ++ conforme (enfin, en supposant que cela se passe numeric_limits::is_iec559bien, c'est le cas, bien que Alf suggère ci-dessus que non): le code C ++ reposant sur IEEE n'est pas C ++ portable et n'a pas le droit s'attendre à ce que les implémentations le fournissent.
Steve Jessop
5
Et Alf a raison, test rapide sur gcc 4.3.4 et is_iec559c'est vrai avec -ffast-math. Donc, le problème ici est que les documents de GCC -ffast-mathdisent seulement que ce n'est pas IEEE / ISO pour les fonctions mathématiques, alors qu'ils devraient dire que ce n'est pas C ++, car son implémentation de numeric_limitsest borked. Je suppose que GCC ne peut pas toujours dire au moment où le modèle est défini, si le backend éventuel a réellement des flottants conformes, et donc n'essaie même pas. IIRC il y a des problèmes similaires dans la liste des bogues en suspens pour la conformité C99 de GCC.
Steve Jessop
1
@Alf, @Steve, je ne savais pas que la norme C ++ n'avait pas de spécification sur les valeurs à virgule flottante. C'est assez choquant pour moi. Il semble mieux gérer IEEE 754 et NaN comme une extension spécifique à la plate-forme plutôt que standard. N'est-ce pas? Et puis-je m'attendre à tout type d'isnan () ou IEEE754 ajouté en C ++ 0x?
Eonil
3
@Eonil: C ++ 0x a toujours par exemple "La représentation de la valeur des types à virgule flottante est définie par l'implémentation". C et C ++ visent tous deux à prendre en charge les implémentations sur des machines sans matériel à virgule flottante, et les flottants IEEE 754 appropriés peuvent être un peu plus lents à émuler que des alternatives raisonnablement précises. La théorie est que vous pouvez affirmer is_iec559si vous avez besoin d'IEEE, dans la pratique qui ne semble pas fonctionner sur GCC. C ++ 0x fait avoir une isnanfonction, mais étant donné que GCC ne met pas en œuvre correctement is_iec559maintenant, je suppose que ce ne sera pas en C ++ 0x soit, et -ffast-mathpourrait bien briser son isnan.
Steve Jessop
39

Il y a un std :: isnan si votre compilateur prend en charge les extensions c99, mais je ne suis pas sûr que mingw le fasse.

Voici une petite fonction qui devrait fonctionner si votre compilateur n'a pas la fonction standard:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}
CTT
la source
6
pourquoi pas juste var! = var?
Brian R. Bondy
8
En faisant cela, il y a une chance que le compilateur optimise la comparaison, retournant toujours vrai.
CTT
23
Non, il n'y en a pas. Un compilateur qui fait cela est cassé. Autant dire qu'il y a une chance que la bibliothèque standard isnanrenvoie le mauvais résultat. Techniquement vrai, le compilateur pourrait être bogué, mais en pratique, ça ne va pas arriver. Identique à var != var. Cela fonctionne parce que c'est ainsi que les valeurs à virgule flottante IEEE sont définies.
2010
29
si -ffast-math est défini, isnan () ne retournera pas le résultat correct pour gcc. Bien sûr, cette optimisation est documentée comme brisant la sémantique IEEE ...
Matthew Herrmann
Si -ffast-math est défini, alors le compilateur est bogué. Ou plutôt, si -ffast-math est défini, tous les paris sont désactivés et vous ne pouvez pas compter sur NaN de toute façon.
Adrian Ratnapala
25

Vous pouvez utiliser numeric_limits<float>::quiet_NaN( )défini dans la limitsbibliothèque standard pour tester avec. Il y a une constante distincte définie pour double.

#include <iostream>
#include <math.h>
#include <limits>

using namespace 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;
   }

   return 0;
}

Je ne sais pas si cela fonctionne sur toutes les plateformes, car je n'ai testé qu'avec g ++ sur Linux.

Bill le lézard
la source
2
Attention, cependant - il semble y avoir un bogue dans numeric_limits dans GCC version 3.2.3, car il renvoie 0,0 pour quiet_NaN. D'après mon expérience, les versions ultérieures de GCC conviennent.
Nathan Kitchen
@Nathan: Bon à savoir. J'utilise la version 4.3.2, donc je suis bien sorti du bois.
Bill the Lizard
18

Vous pouvez utiliser la isnan()fonction, mais vous devez inclure la bibliothèque mathématique C.

#include <cmath>

Comme cette fonction fait partie de C99, elle n'est pas disponible partout. Si votre fournisseur ne fournit pas la fonction, vous pouvez également définir votre propre variante de compatibilité.

inline bool isnan(double x) {
    return x != x;
}
raimue
la source
J'utilisais <cmath> et il n'y a aucun isnan dedans! soit dit en passant , j'ai découvert qu'il est un isnandans <math.h>
Hasen
1
Comme je l'ai dit, cela fait partie de C99. Comme C99 ne fait partie d'aucune norme C ++ actuelle, j'ai fourni l'alternative. Mais comme il est probable que isnan () soit inclus dans une prochaine norme C ++, j'ai mis une directive #ifndef autour.
raimue
12

Le code suivant utilise la définition de NAN (tous les bits d'exposants définis, au moins un ensemble de bits fractionnels) et suppose que sizeof (int) = sizeof (float) = 4. Vous pouvez rechercher NAN dans Wikipedia pour les détails.

bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }

Ian
la source
Je pense que cela fonctionnerait également sur les grandes plateformes endiennes. Le littéral 0x7fffffffresterait simplement en mémoire ff ff ff 7f. valuea le même ordre que le fait 0x7f800000, donc toutes les opérations s'alignent (il n'y a pas d'échange d'octets). Je serais intéressé si quelqu'un pouvait tester cela sur une grande plateforme endienne.
Bryan W. Wagner
0x7fff1234est également un NaN. Il en est de même0xffffffff
Steve Hollasch
12

nan prévention

Ma réponse à cette question est de ne pas utiliser de chèques rétroactifs pournan . Utilisez plutôt des contrôles préventifs pour les divisions du formulaire 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.

nanrésulte de l'opération 0.f/0.f, ou 0.0/0.0. nanest un terrible ennemi de la stabilité de votre code qui doit être détecté et prévenu très soigneusement 1 . Les propriétés de nancela sont différentes des nombres normaux:

  • nanest toxique, (5 * nan= nan)
  • nann'est égal à rien, pas même à lui-même ( nan! = nan)
  • nanpas plus grand que tout ( nan!> 0)
  • nann'est pas moins que n'importe quoi ( nan! <0)

Les 2 dernières propriétés répertoriées sont contre-logiques et entraîneront un comportement étrange du code qui repose sur des comparaisons avec un nannombre (la 3ème dernière propriété est également étrange, mais vous n'allez probablement jamais voir x != x ?dans votre code (sauf si vous vérifiez pour nan (peu fiable))).

Dans mon propre code, j'ai remarqué que les nanvaleurs ont tendance à produire des bogues difficiles à trouver. (Notez que ce n'est pas le cas pour infou -inf. ( -inf<0) renvoie TRUE, (0 < inf) retourne VRAI et même ( -inf< inf) retourne VRAI. Ainsi, selon mon expérience, le comportement du code est souvent toujours comme souhaité).

que faire sous nan

Ce que vous voulez faire 0.0/0.0 doit être traité comme un cas spécial , mais ce que vous faites doit dépendre des nombres que vous attendez du code.

Dans l'exemple ci-dessus, le résultat de ( 0.f/FLT_MIN) sera 0, fondamentalement. Vous voudrez peut- 0.0/0.0être générer à la HUGEplace. Donc,

float x=0.f, y=0.f, z;
if( !x && !y )    // 0.f/0.f case
  z = FLT_MAX ;   // biggest float possible
else
  z = y/x ;       // regular division.

Ainsi , dans ce qui précède, si X 0.f, infaurait pour résultat (qui a très bon / comportement non destructif comme mentionné ci - dessus en fait).

N'oubliez pas que la division entière par 0 provoque une exception d'exécution . Donc, vous devez toujours vérifier la division entière par 0. Ce n'est pas parce que l' 0.0/0.0évaluation est silencieuse nanque vous pouvez être paresseux et ne pas vérifier 0.0/0.0avant que cela ne se produise.

1 Les vérifications de nanvia x != xsont parfois peu fiables ( x != xétant supprimées par certains compilateurs d'optimisation qui ne respectent pas la conformité IEEE, en particulier lorsque le -ffast-mathcommutateur est activé).

bobobobo
la source
Merci de l'avoir signalé; une telle programmation aiderait certainement à résoudre le problème en tant que tel. Mais la prochaine fois, essayez de ne pas trop abuser des fonctionnalités de mise en forme du texte. Changer de taille de police, d'épaisseur et de style comme ça rend la lecture très difficile.
Magnus
4
Notez que 0,0 / 0,0 n'est pas la seule opération qui pourrait entraîner un NaN. La racine carrée d'un nombre négatif renvoie NaN. Le cosinus de + infini renvoie également NaN. l'opération acos (x) où x n'est pas dans la plage [0, pi] peut également entraîner NaN. En un mot, il faut être extrêmement prudent pour examiner également ces opérations potentiellement risquées, non seulement à 0,0 / 0,0.
Boris Dalstein,
Tout à fait d'accord avec Boris. D'après mon expérience, NaN provient presque toujours de quelque chose comme sqrt (-1.302e-53), c'est-à-dire que les résultats de calcul intermédiaires proches de zéro sont introduits dans sqrt sans vérifier la négativité.
hans_meine
1
"Prévenir les NaN" signifie que vous devez entrer dans toutes les opérations arithmétiques de base, pas seulement dans la division. Vous devrez faire attention à ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, entre autres. Être "préventif" avec de telles opérations arithmétiques de base signifie que vous aurez complètement votre performance (et raterez probablement des cas supplémentaires auxquels vous n'aviez pas pensé).
Steve Hollasch
11

Depuis C ++ 14, il existe plusieurs façons de tester si un nombre à virgule flottante valueest un NaN.

De ces façons, seule la vérification des bits de la représentation du nombre fonctionne de manière fiable, comme indiqué dans ma réponse d'origine. En particulier, std::isnanet la vérification souvent proposée v != v, ne fonctionne pas de manière fiable et ne doit pas être utilisée, de peur que votre code cesse de fonctionner correctement lorsque quelqu'un décide que l'optimisation en virgule flottante est nécessaire et demande au compilateur de le faire. Cette situation peut changer, les compilateurs peuvent devenir plus conformes, mais pour ce problème qui ne s'est pas produit au cours des 6 années écoulées depuis la réponse d'origine.

Pendant environ 6 ans, ma réponse initiale était la solution choisie pour cette question, qui était OK. Mais récemment, une réponse très appréciée recommandant le v != vtest non fiable a été sélectionnée. D'où cette réponse encore plus à jour (nous avons désormais les standards C ++ 11 et C ++ 14, et C ++ 17 à l'horizon).


Les principaux moyens de vérifier la non-NaN, à partir de C ++ 14, sont les suivants:

  • std::isnan(value) )
    est le moyen de bibliothèque standard prévu depuis C ++ 11. isnanapparemment en conflit avec la macro Posix du même nom, mais en pratique ce n'est pas un problème. Le problème principal est que lorsque l'optimisation arithmétique à virgule flottante est demandée, alors avec au moins un compilateur principal, à savoir g ++, std::isnan renvoie l' falseargument NaN .

  • (fpclassify(value) == FP_NAN) )
    Souffre du même problème std::isnan, c'est-à-dire n'est pas fiable.

  • (value != value) )
    Recommandé dans de nombreuses réponses SO. Souffre du même problème std::isnan, c'est-à-dire n'est pas fiable.

  • (value == Fp_info::quiet_NaN()) )
    Il s'agit d'un test qui, avec un comportement standard, ne devrait pas détecter les NaN, mais qui, avec le comportement optimisé, pourrait détecter les NaN (en raison du code optimisé comparant directement les représentations de niveau de bit), et peut-être combiné avec une autre façon de couvrir le comportement standard non optimisé , pourrait détecter de manière fiable NaN. Malheureusement, il s'est avéré ne pas fonctionner de manière fiable.

  • (ilogb(value) == FP_ILOGBNAN) )
    Souffre du même problème std::isnan, c'est-à-dire n'est pas fiable.

  • isunordered(1.2345, value) )
    Souffre du même problème std::isnan, c'est-à-dire n'est pas fiable.

  • is_ieee754_nan( value ) )
    Ce n'est pas une fonction standard. C'est la vérification des bits selon la norme IEEE 754. Il est complètement fiable mais le code dépend quelque peu du système.


Dans le code de test complet suivant, «succès» consiste à savoir si une expression signale Nan-ness de la valeur. Pour la plupart des expressions, cette mesure de succès, l'objectif de détecter les NaN et uniquement les NaN, correspond à leur sémantique standard. Pour l' (value == Fp_info::quiet_NaN()) )expression, cependant, le comportement standard est qu'il ne fonctionne pas comme un détecteur NaN.

#include <cmath>        // std::isnan, std::fpclassify
#include <iostream>
#include <iomanip>      // std::setw
#include <limits>
#include <limits.h>     // CHAR_BIT
#include <sstream>
#include <stdint.h>     // uint64_t
using namespace std;

#define TEST( x, expr, expected ) \
    [&](){ \
        const auto value = x; \
        const bool result = expr; \
        ostringstream stream; \
        stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
        cout \
            << setw( 60 ) << stream.str() << "  " \
            << (result == expected? "Success" : "FAILED") \
            << endl; \
    }()

#define TEST_ALL_VARIABLES( expression ) \
    TEST( v, expression, true ); \
    TEST( u, expression, false ); \
    TEST( w, expression, false )

using Fp_info = numeric_limits<double>;

inline auto is_ieee754_nan( double const x )
    -> bool
{
    static constexpr bool   is_claimed_ieee754  = Fp_info::is_iec559;
    static constexpr int    n_bits_per_byte     = CHAR_BIT;
    using Byte = unsigned char;

    static_assert( is_claimed_ieee754, "!" );
    static_assert( n_bits_per_byte == 8, "!" );
    static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );

    #ifdef _MSC_VER
        uint64_t const bits = reinterpret_cast<uint64_t const&>( x );
    #else
        Byte bytes[sizeof(x)];
        memcpy( bytes, &x, sizeof( x ) );
        uint64_t int_value;
        memcpy( &int_value, bytes, sizeof( x ) );
        uint64_t const& bits = int_value;
    #endif

    static constexpr uint64_t   sign_mask       = 0x8000000000000000;
    static constexpr uint64_t   exp_mask        = 0x7FF0000000000000;
    static constexpr uint64_t   mantissa_mask   = 0x000FFFFFFFFFFFFF;

    (void) sign_mask;
    return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
}

auto main()
    -> int
{
    double const v = Fp_info::quiet_NaN();
    double const u = 3.14;
    double const w = Fp_info::infinity();

    cout << boolalpha << left;
    cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
    cout << endl;;
    TEST_ALL_VARIABLES( std::isnan(value) );                    cout << endl;
    TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) );        cout << endl;
    TEST_ALL_VARIABLES( (value != value) );                     cout << endl;
    TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) );      cout << endl;
    TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) );        cout << endl;
    TEST_ALL_VARIABLES( isunordered(1.2345, value) );           cout << endl;
    TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
}

Résultats avec g ++ (notez à nouveau que le comportement standard de (value == Fp_info::quiet_NaN())est qu'il ne fonctionne pas comme un détecteur NaN, c'est juste très intéressant ici):

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> g ++ --version | trouver "++"
g ++ (x86_64-win32-sjlj-rev1, construit par le projet MinGW-W64) 6.3.0

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> g ++ foo.cpp && a
Le compilateur revendique IEEE 754 = true

v = nan, (std :: isnan (valeur)) = vrai succès
u = 3,14, (std :: isnan (valeur)) = faux succès
w = inf, (std :: isnan (value)) = false Réussite

v = nan, ((fpclassify (value) == 0x0100)) = true Success
u = 3,14, ((fpclassify (value) == 0x0100)) = false Success
w = inf, ((fpclassify (value) == 0x0100)) = false Success

v = nan, ((valeur! = valeur)) = vrai succès
u = 3,14, ((valeur! = valeur)) = faux succès
w = inf, ((valeur! = valeur)) = faux Succès

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((value == Fp_info :: quiet_NaN ())) = false Success
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Success

v = nan, ((ilogb (valeur) == ((int) 0x80000000))) = vrai succès
u = 3,14, ((ilogb (valeur) == ((int) 0x80000000))) = faux succès
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Success

v = nan, (isunordered (1.2345, value)) = true Success
u = 3,14, (isunordered (1.2345, value)) = false Success
w = inf, (isunordered (1.2345, value)) = false Success

v = nan, (is_ieee754_nan (value)) = vrai succès
u = 3,14, (is_ieee754_nan (valeur)) = faux succès
w = inf, (is_ieee754_nan (value)) = false Success

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> g ++ foo.cpp -fast-math && a
Le compilateur revendique IEEE 754 = true

v = nan, (std :: isnan (valeur)) = false ÉCHEC
u = 3,14, (std :: isnan (valeur)) = faux succès
w = inf, (std :: isnan (value)) = false Réussite

v = nan, ((fpclassify (value) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (value) == 0x0100)) = false Success
w = inf, ((fpclassify (value) == 0x0100)) = false Success

v = nan, ((valeur! = valeur)) = faux ÉCHEC
u = 3,14, ((valeur! = valeur)) = faux succès
w = inf, ((valeur! = valeur)) = faux Succès

v = nan, ((value == Fp_info :: quiet_NaN ())) = true Success
u = 3,14, ((value == Fp_info :: quiet_NaN ())) = true FAILED
w = inf, ((value == Fp_info :: quiet_NaN ())) = true FAILED

v = nan, ((ilogb (valeur) == ((int) 0x80000000))) = vrai succès
u = 3,14, ((ilogb (valeur) == ((int) 0x80000000))) = faux succès
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Success

v = nan, (isunordered (1.2345, value)) = false FAILED
u = 3,14, (isunordered (1.2345, value)) = false Success
w = inf, (isunordered (1.2345, value)) = false Success

v = nan, (is_ieee754_nan (value)) = vrai succès
u = 3,14, (is_ieee754_nan (valeur)) = faux succès
w = inf, (is_ieee754_nan (value)) = false Success

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> _

Résultats avec Visual C ++:

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> cl / nologo- 2> & 1 | trouver "++"
Microsoft (R) C / C ++ Optimizing Compiler Version 19.00.23725 pour x86

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> cl foo.cpp / Feb && b
foo.cpp
Le compilateur revendique IEEE 754 = true

v = nan, (std :: isnan (valeur)) = vrai succès
u = 3,14, (std :: isnan (valeur)) = faux succès
w = inf, (std :: isnan (value)) = false Réussite

v = nan, ((fpclassify (value) == 2)) = true Success
u = 3,14, ((fpclassify (value) == 2)) = false Success
w = inf, ((fpclassify (value) == 2)) = false Success

v = nan, ((valeur! = valeur)) = vrai succès
u = 3,14, ((valeur! = valeur)) = faux succès
w = inf, ((valeur! = valeur)) = faux Succès

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((value == Fp_info :: quiet_NaN ())) = false Success
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Success

v = nan, ((ilogb (valeur) == 0x7fffffff)) = vrai succès
u = 3,14, ((ilogb (valeur) == 0x7fffffff)) = faux succès
w = inf, ((ilogb (valeur) == 0x7fffffff)) = true ÉCHEC

v = nan, (isunordered (1.2345, value)) = true Success
u = 3,14, (isunordered (1.2345, value)) = false Success
w = inf, (isunordered (1.2345, value)) = false Success

v = nan, (is_ieee754_nan (value)) = vrai succès
u = 3,14, (is_ieee754_nan (valeur)) = faux succès
w = inf, (is_ieee754_nan (value)) = false Success

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> cl foo.cpp / Feb / fp: rapide && b
foo.cpp
Le compilateur revendique IEEE 754 = true

v = nan, (std :: isnan (valeur)) = vrai succès
u = 3,14, (std :: isnan (valeur)) = faux succès
w = inf, (std :: isnan (value)) = false Réussite

v = nan, ((fpclassify (value) == 2)) = true Success
u = 3,14, ((fpclassify (value) == 2)) = false Success
w = inf, ((fpclassify (value) == 2)) = false Success

v = nan, ((valeur! = valeur)) = vrai succès
u = 3,14, ((valeur! = valeur)) = faux succès
w = inf, ((valeur! = valeur)) = faux Succès

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((value == Fp_info :: quiet_NaN ())) = false Success
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Success

v = nan, ((ilogb (valeur) == 0x7fffffff)) = vrai succès
u = 3,14, ((ilogb (valeur) == 0x7fffffff)) = faux succès
w = inf, ((ilogb (valeur) == 0x7fffffff)) = true ÉCHEC

v = nan, (isunordered (1.2345, value)) = true Success
u = 3,14, (isunordered (1.2345, value)) = false Success
w = inf, (isunordered (1.2345, value)) = false Success

v = nan, (is_ieee754_nan (value)) = vrai succès
u = 3,14, (is_ieee754_nan (valeur)) = faux succès
w = inf, (is_ieee754_nan (value)) = false Success

[C: \ my \ forums \ so \ 282 (détecter NaN)]
> _

Pour résumer les résultats ci-dessus, seuls les tests directs de la représentation au niveau du bit, en utilisant la is_ieee754_nanfonction définie dans ce programme de test, ont fonctionné de manière fiable dans tous les cas avec g ++ et Visual C ++.


Addendum:
Après avoir publié ce qui précède, j'ai pris connaissance d'un autre test possible pour NaN, mentionné dans une autre réponse ici, à savoir ((value < 0) == (value >= 0)). Cela s'est avéré fonctionner correctement avec Visual C ++ mais a échoué avec l' -ffast-mathoption de g ++ . Seuls les tests Bitpattern directs fonctionnent de manière fiable.

Santé et hth. - Alf
la source
7
inline bool IsNan(float f)
{
    const uint32 u = *(uint32*)&f;
    return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF);    // Both NaN and qNan.
}

inline bool IsNan(double d)
{
    const uint64 u = *(uint64*)&d;
    return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}

Cela fonctionne si sizeof(int)est 4 et sizeof(long long)est 8.

Pendant l'exécution, ce n'est qu'une comparaison, les castings ne prennent pas de temps. Il modifie simplement la configuration des drapeaux de comparaison pour vérifier l'égalité.

ST3
la source
Notez également qu'il est limité à la représentation IEEE 754.
Bravo et hth. - Alf
Notez que ce transtypage rompt la règle d'aliasing stricte de g ++, et que le compilateur est connu pour faire Unmentionable Things ™ lorsqu'il détecte l'UB formelle. Au lieu de conversions efficaces, avec g ++ vous devez utiliser memcpy, à travers un tableau d'octets pour être sûr. Code pour cela dans ma réponse n ° 2 .
Bravo et hth. - Alf
4

Une solution possible qui ne dépendrait pas de la représentation IEEE spécifique pour NaN utilisée serait la suivante:

template<class T>
bool isnan( T f ) {
    T _nan =  (T)0.0/(T)0.0;
    return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
Dan Nathan
la source
La virgule flottante simple précision a plus de 8 millions de représentations de bits légitimes et différentes pour NaN, vous devrez donc ajouter quelques comparaisons supplémentaires. :)
Steve Hollasch
4

Étant donné que (x! = X) n'est pas toujours garanti pour NaN (comme si vous utilisez l'option -ffast-math), j'ai utilisé:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

Les nombres ne peuvent pas être à la fois <0 et> = 0, donc cette vérification n'est réussie que si le nombre n'est ni inférieur, ni supérieur ou égal à zéro. Ce qui est fondamentalement pas de nombre du tout, ou NaN.

Vous pouvez également l'utiliser si vous préférez:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

Je ne sais pas comment cela est affecté par -ffast-math, donc votre kilométrage peut varier.

Jerramy
la source
Ceci est en fait imparfait de la même manière qu'il l' f != fest aussi. J'ai vu llvm optimiser un morceau de code presque identique. L'optimiseur peut propager les informations sur la première comparaison et comprendre que la deuxième comparaison peut ne jamais être vraie si la première l'est. (si le compilateur obéit strictement aux règles IEEE f != fest de toute façon beaucoup plus simple)
Markus
Ne fonctionne pas avec l' -ffast-mathoption de g ++ . Fonctionne avec Visual C ++. Voir ( stackoverflow.com/a/42138465/464581 ).
Bravo et hth. - Alf
3

Quant à moi, la solution pourrait être une macro pour la rendre explicitement en ligne et donc assez rapide. Il fonctionne également pour tout type de flotteur. Elle repose sur le fait que le seul cas où une valeur n'est pas égale à elle-même est lorsque la valeur n'est pas un nombre.

#ifndef isnan
  #define isnan(a) (a != a)
#endif
user1705817
la source
C'est l'une des meilleures réponses à cette question! Merci pour le partage.
Henri Menke
2
D'autres réponses indiquent que cela peut échouer avec le jeu d'options -ffast-math.
Technophile
3

Cela marche:

#include <iostream>
#include <math.h>
using namespace std;

int main ()
{
  char ch='a';
  double val = nan(&ch);
  if(isnan(val))
     cout << "isnan" << endl;

  return 0;
}

sortie: isnan

edW
la source
1

Il me semble que la meilleure approche véritablement multiplateforme serait d'utiliser une union et de tester la configuration binaire du double pour vérifier les NaN.

Je n'ai pas testé cette solution en profondeur, et il peut y avoir une façon plus efficace de travailler avec les modèles de bits, mais je pense que cela devrait fonctionner.

#include <stdint.h>
#include <stdio.h>

union NaN
{
    uint64_t bits;
    double num;
};

int main()
{
    //Test if a double is NaN
    double d = 0.0 / 0.0;
    union NaN n;
    n.num = d;
    if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
    {
        printf("NaN: %f", d);
    }

    return 0;
}
Sheldon Juncker
la source
Notez que "c'est un comportement indéfini à lire du membre du syndicat qui n'a pas été écrit le plus récemment". Par conséquent, cette utilisation d'un uniontype de jeu de mots entre deux types peut ne pas fonctionner comme souhaité (: sad_panda :). La bonne façon (bien que pas aussi portable que souhaité) serait d'éviter complètement l'union et de faire un memcpy de doubledans une uint64_tvariable différente , puis de faire le test en utilisant cette variable auxiliaire.
Eljay
0

Sur x86-64, vous pouvez avoir des méthodes extrêmement rapides pour vérifier NaN et infini, qui fonctionnent indépendamment de l' -ffast-mathoption du compilateur. ( f != f, std::isnan, std::isinfCédez toujours falseavec -ffast-math).


Les tests pour NaN, l'infini et les nombres finis peuvent facilement être effectués en vérifiant l'exposant maximum. l'infini est l'exposant maximum avec une mantisse nulle, NaN est l'exposant maximum et une mantisse non nulle. L'exposant est stocké dans les bits suivants après le bit de signe le plus haut, afin que nous puissions simplement déplacer à gauche pour se débarrasser du bit de signe et faire de l'exposant les bits du haut, aucun masquage ( operator&) n'est nécessaire:

static inline uint64_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;
}

static inline uint32_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;
}

constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
constexpr uint32_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.
static inline bool isnan2(double a)    { return load_ieee754_rep(a) << 1  > inf_double_shl1; }
static inline bool isinf2(double a)    { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1  < inf_double_shl1; }
static inline bool isnan2(float a)     { return load_ieee754_rep(a) << 1  > inf_float_shl1; }
static inline bool isinf2(float a)     { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
static inline bool isfinite2(float a)  { return load_ieee754_rep(a) << 1  < inf_float_shl1; }

Les stdversions de isinfet isfinitechargent 2 double/floatconstantes à partir du .datasegment et dans le pire des cas, elles peuvent provoquer 2 échecs de cache de données. Les versions ci-dessus ne chargent aucune donnée inf_double_shl1et les inf_float_shl1constantes sont encodées en tant qu'opérandes immédiats dans les instructions d'assemblage.


Plus rapide isnan2est juste 2 instructions de montage:

bool isnan2(double a) {
    bool r;
    asm(".intel_syntax noprefix"
        "\n\t ucomisd %1, %1"
        "\n\t setp %b0"
        "\n\t .att_syntax prefix"
        : "=g" (r)
        : "x" (a)
        : "cc"
        );
    return r;
}

Utilise le fait que l' ucomisdinstruction définit l'indicateur de parité si un argument est NaN. C'est ainsi que std::isnanfonctionne lorsqu'aucune -ffast-mathoption n'est spécifiée.

Maxim Egorushkin
la source
-1

La norme IEEE dit que lorsque l'exposant est tout 1s et que la mantisse n'est pas nulle, le nombre est a NaN. Le double est le 1bit de signe, 11les bits d'exposant et les 52bits de mantisse. Vérifiez un peu.

bop
la source
-3

Comme les commentaires ci-dessus indiquent a! = A ne fonctionnera pas dans g ++ et certains autres compilateurs, mais cette astuce devrait. Ce n'est peut-être pas aussi efficace, mais c'est quand même un moyen:

bool IsNan(float a)
{
    char s[4];
    sprintf(s, "%.3f", a);
    if (s[0]=='n') return true;
    else return false;
}

Fondamentalement, dans g ++ (je ne suis pas sûr pour les autres cependant) printf affiche 'nan' aux formats% d ou% .f si la variable n'est pas un entier / flottant valide. Par conséquent, ce code vérifie que le premier caractère de la chaîne est «n» (comme dans «nan»)

ZenJ
la source
2
Cela ne provoquerait-il pas un débordement de tampon si a = 234324.0f?
Mazyod
Oui tu le veux, ou 340282346638528859811704183484516925440.000si a = FLT_MAX. Il devrait utiliser char s[7]; sprintf(s, "%.0g", a);, qui sera de 6 heures si a=-FLT_MAX, ou-3e+38
bobobobo
-3

Cela détecte l'infini et également NaN dans Visual Studio en vérifiant qu'il est dans les doubles limites:

//#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;
ingénieur mathématicien
la source
Vérifiez la définition de FLT_MIN, DBL_MINet LDBL_MINplus attentivement. Ces valeurs sont définies comme étant les plus petites valeurs normalisées pour chaque type. Par exemple, la simple précision a plus de 8 millions de valeurs de denorm légitimes qui sont supérieures à zéro et inférieures à FLT_MIN(et ne sont pas NaN).
Steve Hollasch