Comment utiliser nan et inf en C?

89

J'ai une méthode numérique qui pourrait retourner nan ou inf s'il y avait une erreur, et pour les tests intentionnels, j'aimerais la forcer temporairement à retourner nan ou inf pour m'assurer que la situation est gérée correctement. Existe-t-il un moyen fiable et indépendant du compilateur de créer des valeurs de nan et inf en C?

Après avoir cherché sur Google pendant environ 10 minutes, je n'ai pu trouver que des solutions dépendantes du compilateur.

Graphiques Noob
la source
les flotteurs ne sont pas définis par la norme C. Il n'y a donc pas de moyen indépendant du compilateur de faire ce que vous voulez.
Johan Kotlinski

Réponses:

86

Vous pouvez tester si votre implémentation l'a:

#include <math.h>
#ifdef NAN
/* NAN is supported */
#endif
#ifdef INFINITY
/* INFINITY is supported */
#endif

L'existence de INFINITYest garantie par C99 (ou le dernier brouillon au moins), et "se développe en une expression constante de type float représentant l'infini positif ou non signé, si disponible; sinon, en une constante positive de type float qui déborde au moment de la traduction."

NAN peut ou non être défini et "est défini si et seulement si l'implémentation prend en charge les NaN silencieux pour le type float. Il se développe en une expression constante de type float représentant un NaN silencieux."

Notez que si vous comparez des valeurs à virgule flottante, et faites:

a = NAN;

même à ce moment là,

a == NAN;

c'est faux. Une façon de vérifier NaN serait:

#include <math.h>
if (isnan(a)) { ... }

Vous pouvez également faire: a != apour tester si aest NaN.

Il y a aussi isfinite(), isinf(), isnormal()et signbit()macros math.hdans C99.

C99 a également des nanfonctions:

#include <math.h>
double nan(const char *tagp);
float nanf(const char *tagp);
long double nanl(const char *tagp);

(Référence: n1256).

Docs INFINITY Docs NAN

Alok Singhal
la source
2
Excellente réponse. La référence pour les macros NAN et INFINITY est C99 §7.12 paragraphes 4 et 5. Outre (isnan (a)), vous pouvez également vérifier NaN en utilisant (a! = A) sur une implémentation conforme de C.
Stephen Canon
23
Pour l'amour de la lisibilité, a != ane doit JAMAIS être utilisé.
Chris Kerekes
1
@ChrisKerekes: malheureusement, certains d'entre nous ont NAN mais pas isnan (). Oui, c'est 2017. :(
eff
C ne nécessite pas, quand aest un not-a-number, a == NANde renvoyer false. IEEE l'exige. Même les implémentations qui adhèrent à l'IEEE le font principalement . Lorsqu'il isnan()n'est pas implémenté, il est préférable d'encapsuler le test que de coder directement a == NAN.
chux
34

Il n'y a pas de moyen indépendant du compilateur de faire cela, car ni les normes C (ni C ++) ne disent que les types mathématiques à virgule flottante doivent prendre en charge NAN ou INF.

Edit: Je viens de vérifier le libellé du standard C ++, et il dit que ces fonctions (membres de la classe modèle numeric_limits):

quiet_NaN() 
signalling_NaN()

renverra les représentations NAN "si disponibles". Il ne développe pas ce que signifie «si disponible», mais probablement quelque chose comme «si le représentant FP de l'implémentation les soutient». De même, il existe une fonction:

infinity() 

qui renvoie un représentant INF positif "si disponible".

Ceux-ci sont tous deux définis dans l'en- <limits>tête - je suppose que le standard C a quelque chose de similaire (probablement aussi "si disponible") mais je n'ai pas de copie du standard C99 actuel.


la source
C'est décevant et surprenant. C et C ++ ne sont-ils pas conformes aux nombres à virgule flottante IEEE, qui ont une représentation standard pour nan et inf?
Graphics Noob
14
En C99, l' en- tête de C <math.h>définit nan(), nanf()et nanl()que le retour des représentations différentes de NaN (comme double, floatet intrespectivement), et l' infini (si avaliable) peut être retourné en générant une avec log(0)ou quelque chose. Il n'y a pas de moyen standard de les vérifier, même dans C99. L'en- <float.h>tête ( <limits.h>pour les types intégraux) est malheureusement muet sur les valeurs infet nan.
Chris Lutz
Wow, c'est une grosse confusion. nanl()renvoie a long double, pas un intcomme mon commentaire dit. Je ne sais pas pourquoi je ne m'en suis pas rendu compte quand je l'ai tapé.
Chris Lutz
@Chris, voir ma réponse pour C99.
Alok Singhal
2
@IngeHenriksen - À peu près sûr que Microsoft a déclaré qu'il n'avait aucune intention de faire en sorte que VC ++ prenne en charge C99.
Chris Lutz
24

Cela fonctionne à la fois pour floatet double:

double NAN = 0.0/0.0;
double POS_INF = 1.0 /0.0;
double NEG_INF = -1.0/0.0;

Edit: Comme quelqu'un l'a déjà dit, l'ancienne norme IEEE disait que de telles valeurs devraient lever des pièges. Mais les nouveaux compilateurs désactivent presque toujours les traps et renvoient les valeurs données car le trapping interfère avec la gestion des erreurs.

Thorsten S.
la source
Le recouvrement était une option de gestion des erreurs autorisée sous 754-1985. Le comportement utilisé par la plupart des matériels / compilateurs modernes était également autorisé (et était le comportement préféré de nombreux membres du comité). De nombreux développeurs ont supposé à tort que le piégeage était nécessaire en raison de l'utilisation malheureuse du terme «exceptions» dans la norme. Cela a été grandement clarifié dans la version révisée 754-2008.
Stephen Canon
Salut, Stephen, vous avez raison, mais la norme dit également: "Un utilisateur devrait pouvoir demander une interruption sur l'une des cinq exceptions en spécifiant un gestionnaire pour cela. Il devrait pouvoir demander qu'un gestionnaire existant soit désactivé , enregistré ou restauré. Il doit également être en mesure de déterminer si un gestionnaire d'interruptions spécifique pour une exception désignée a été activé. " "devrait" tel que défini (2. Définitions) signifie "fortement recommandé" et sa mise en œuvre ne devrait être omise que si l'architecture, etc., la rend impraticable. 80x86 prend entièrement en charge le standard, il n'y a donc aucune raison pour que C ne le supporte pas.
Thorsten
Je conviens que C devrait exiger 754 (2008) virgule flottante, mais il y a de bonnes raisons de ne pas le faire; Plus précisément, C est utilisé dans toutes sortes d'environnements autres que x86 - y compris les périphériques embarqués qui n'ont pas de matériel à virgule flottante et les périphériques de traitement du signal où les programmeurs ne veulent même pas utiliser de virgule flottante. À tort ou à raison, ces utilisations expliquent beaucoup d'inertie dans la spécification du langage.
Stephen Canon
Je ne sais pas pourquoi la réponse la plus élevée l'a fait. Cela ne donne aucun moyen de produire les valeurs demandées. Cette réponse le fait.
drysdam
#define is_nan(x) ((x) != (x))peut être utile comme test simple et portable pour NAN.
Bob Stein
21

Un moyen indépendant du compilateur, mais pas un moyen indépendant du processeur pour les obtenir:

int inf = 0x7F800000;
return *(float*)&inf;

int nan = 0x7F800001;
return *(float*)&nan;

Cela devrait fonctionner sur n'importe quel processeur utilisant le format à virgule flottante IEEE 754 (ce que fait x86).

MISE À JOUR: Testé et mis à jour.

Aaron
la source
2
@WaffleMatt - pourquoi ce port ne serait-il pas entre 32/64 bits? Le flotteur simple précision IEEE 754 est de 32 bits quelle que soit la taille d'adressage du processeur sous-jacent.
Aaron
6
Casting vers (float &)? Cela ne me ressemble pas à C. Vous avez besoinint i = 0x7F800000; return *(float *)&i;
Chris Lutz
6
Notez que 0x7f800001c'est ce qu'on appelle un NaN de signalisation dans la norme IEEE-754. Bien que la plupart des bibliothèques et du matériel ne prennent pas en charge la signalisation des NaN, il est probablement préférable de renvoyer un NaN silencieux comme 0x7fc00000.
Stephen Canon
6
Attention: cela peut déclencher un comportement indéfini par violation de règles strictes d'alias . Le moyen recommandé (et le mieux pris en charge par les compilateurs) de faire des punitions de type consiste à utiliser les membres du syndicat .
ulidtko
2
En plus du problème d'alias strict signalé par @ulidtko, cela suppose que la cible utilise le même endian pour les entiers en virgule flottante, ce qui n'est certainement pas toujours le cas.
mr.stobbe
15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
J.Kraftcheck
la source
4
C'est une solution portable intelligente! C99 nécessite strtodet convertit les NaN et les Inf.
ulidtko
1
Non pas qu'il y ait un inconvénient avec cette solution; ce ne sont pas des constantes. Vous ne pouvez pas utiliser ces valeurs pour initialiser une variable globale par exemple (ou pour initialiser un tableau).
Marc
1
@Marc. Vous pouvez toujours avoir une fonction d'initialisation qui les appelle une fois et les définit dans l'espace de noms global. C'est un inconvénient très pratique.
Mad Physicist le
3
<inf.h>

/* IEEE positive infinity.  */

#if __GNUC_PREREQ(3,3)
# define INFINITY   (__builtin_inff())
#else
# define INFINITY   HUGE_VALF
#endif

et

<bits/nan.h>
#ifndef _MATH_H
# error "Never use <bits/nan.h> directly; include <math.h> instead."
#endif


/* IEEE Not A Number.  */

#if __GNUC_PREREQ(3,3)

# define NAN    (__builtin_nanf (""))

#elif defined __GNUC__

# define NAN \
  (__extension__                                  \
   ((union { unsigned __l __attribute__ ((__mode__ (__SI__))); float __d; })  \
    { __l: 0x7fc00000UL }).__d)

#else

# include <endian.h>

# if __BYTE_ORDER == __BIG_ENDIAN
#  define __nan_bytes       { 0x7f, 0xc0, 0, 0 }
# endif
# if __BYTE_ORDER == __LITTLE_ENDIAN
#  define __nan_bytes       { 0, 0, 0xc0, 0x7f }
# endif

static union { unsigned char __c[4]; float __d; } __nan_union
    __attribute_used__ = { __nan_bytes };
# define NAN    (__nan_union.__d)

#endif  /* GCC.  */
4pie0
la source
0

Je suis également surpris qu'il ne s'agisse pas de constantes de temps de compilation. Mais je suppose que vous pouvez créer ces valeurs assez facilement en exécutant simplement une instruction qui renvoie un résultat aussi invalide. Diviser par 0, log de 0, bronzage de 90, ce genre de chose.

Carl Smotricz
la source
0

J'utilise habituellement

#define INFINITY (1e999)

ou

const double INFINITY = 1e999

qui fonctionne au moins dans les contextes IEEE 754 car la valeur double représentable la plus élevée est à peu près 1e308. 1e309fonctionnerait aussi bien, comme le ferait 1e99999, mais trois neuf sont suffisants et mémorables. Puisqu'il s'agit d'un double littéral (dans le #definecas) ou d'une Infvaleur réelle , il restera infini même si vous utilisez des flottants de 128 bits («long double»).

Douglas Bagnall
la source
1
C'est très dangereux, à mon avis. Imaginez comment quelqu'un migre votre code vers des flottants de 128 bits en une vingtaine d'années (après que votre code ait traversé une évolution incroyablement complexe, aucune des étapes dont vous avez pu prédire aujourd'hui). Soudainement, la plage de l'exposant augmente considérablement et tous vos 1e999littéraux ne sont plus arrondis à +Infinity. Selon les lois de Murphy, cela brise un algorithme. Pire encore: un programmeur humain effectuant la construction "128 bits" ne repérera probablement pas cette erreur à l'avance. C'est-à-dire qu'il sera probablement trop tard lorsque cette erreur sera trouvée et reconnue. Très dangereux.
ulidtko
1
Bien sûr, le pire des cas ci-dessus pourrait être loin d'être réaliste. Mais quand même, considérez les alternatives! Il vaut mieux rester prudent.
ulidtko
2
"Dans 20 ans", heh. Allons. Cette réponse n'est pas si mauvaise.
alecov
@ulidtko Je n'aime pas ça non plus, mais vraiment?
Iharob Al Asimi
0

Voici un moyen simple de définir ces constantes, et je suis presque sûr que c'est portable:

const double inf = 1.0/0.0;
const double nan = 0.0/0.0;

Quand j'exécute ce code:

printf("inf  = %f\n", inf);
printf("-inf = %f\n", -inf);
printf("nan  = %f\n", nan);
printf("-nan = %f\n", -nan);

Je reçois:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Patrick Chkoreff
la source