如何在C语言中使用nan和inf?

112

我有一种数值方法,如果出现错误,它可能会返回nan或inf,为了测试目的,我想临时强制它返回nan或inf,以确保情况被正确处理。在C语言中,是否有一种可靠的、独立于编译器的方法来创建nan和inf的值?

经过大约10分钟的谷歌搜索,我只能找到与编译器相关的解决方案。


浮点数在C标准中没有定义。因此,没有编译器独立的方法可以实现你想要的功能。 - Johan Kotlinski
10个回答

108

你可以测试一下你的实现是否具备它:

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

C99(或最新的草案)保证了INFINITY的存在,“如果可用,则扩展为表示正无穷大或无符号无穷大的float类型的常量表达式;否则,扩展为在翻译时溢出的float类型的正常量。”

NAN可能被定义或未被定义,“当且仅当实现支持float类型的quiet NaN时才定义。它扩展为表示静默NaN的float类型的常量表达式。”

请注意,如果您正在比较浮点值,并进行以下操作:

a = NAN;

即便如此,

a == NAN;

为false。检查NaN的一种方法是:

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

您还可以使用 a != a 来测试 a 是否为 NaN。

C99 中的 math.h 还有一些宏,如 isfinite()isinf()isnormal()signbit()

C99 还提供了 nan 函数:

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

(参考:n1256)。

INFINITY文档 NAN文档


5
很好的回答。NAN和INFINITY宏的参考是C99 §7.12第4和第5段。除了使用(isnan(a))之外,在符合C规范的实现中,还可以使用(a != a)来检查NaN。 - Stephen Canon
35
为了更好的可读性,a != a 绝对不能使用。 - Chris Kerekes
1
@ChrisKerekes:可悲的是,我们中的一些人拥有NAN但没有isnan()函数。是的,这是2017年。 :( - eff
补充一下chux所说的,即使在使用相同编译器的IEEE 754系统上,a != a 在两个不同的优化级别下可能会有不同的行为。请谨慎使用此比较! - Will Eccles
谢谢@WillEccles,我不知道那个! - Alok Singhal
显示剩余2条评论

34

没有编译器独立的方法来实现这一点,因为C(或C++)标准都没有规定浮点数类型必须支持NAN或INF。

编辑:我刚刚检查了C++标准的措辞,它说这些函数(模板类numeric_limits的成员):

quiet_NaN() 
signalling_NaN()

将返回NAN表示“如果可用”。它没有详细说明“如果可用”的含义,但可能是指“如果实现的FP表示支持它们”。同样,还有一个函数:

infinity() 

它会返回一个正的 INF 表示“如果可用”。

这两个值在 <limits> 头文件中定义 - 我猜测 C 标准也有类似的东西(可能也是“如果可用”),但我没有当前 C99 标准的副本。


这令人失望和惊讶。C和C++难道不符合IEEE浮点数标准,其中包括nan和inf的标准表示吗? - Graphics Noob
15
在 C99 中,C 标准头文件 <math.h> 定义了 nan()nanf()nanl() 三个函数来返回不同表示的 NaN(分别作为 doublefloatint)。如果可用,通过使用类似 log(0) 的方法可以返回无穷大。即使在 C99 中,也没有标准的方法来检查它们。很遗憾的是,C 标准头文件 <float.h>(对于整型类型则是 <limits.h>)没有提及 infnan 值。 - Chris Lutz
哇,这真是一个大混乱。nanl()返回的是long double,而不是像我的评论所说的int。我不知道为什么在打字时没有意识到这一点。 - Chris Lutz
@Chris,请看我对C99的回答。 - Alok Singhal
@ChrisLutz 这些定义不在VC++ 2012的math.h中。但是在limits.h中有一些,请参见http://msdn.microsoft.com/en-us/library/6hthw3cb(v=vs.110).aspx。例如:`float n = numeric_limits<float>::infinity();` - user152949
2
@IngeHenriksen - 我很确定微软已经声明不打算支持C99的VC++。 - Chris Lutz

32

这适用于floatdouble

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

编辑: 正如其他人所说,旧的IEEE标准规定这些值应该引发陷阱。但是,新编译器几乎总是关闭陷阱并返回给定值,因为陷阱会干扰错误处理。


在 754-1985 标准下,捕获是一种允许的错误处理选项之一。大多数现代硬件/编译器使用的行为也是允许的(并且是委员会许多成员首选的行为)。许多实现者错误地认为由于标准中不幸使用了“异常”这个术语,因此需要捕获。在修订后的 754-2008 中已经得到了极大的澄清。 - Stephen Canon
嗨,史蒂芬,你说得对,但标准也指出: “用户应该能够通过指定处理程序来请求任何五个异常中的任何一个陷阱。他应该能够请求禁用、保存或恢复现有的处理程序。他还应该能够确定是否启用了指定异常的特定陷阱处理程序。” “应该”在定义(2.定义)中意味着“强烈推荐”,只有在架构等方面使其不切实际时才应该省略其实现。80x86完全支持标准,因此没有理由C不支持它。 - Thorsten S.
我同意C语言应该需要754(2008)浮点数,但是有很多好的理由不这样做;具体来说,C语言被用于各种环境,而不仅仅是x86 -- 包括没有硬件浮点运算的嵌入式设备和信号处理设备,在这些设备上程序员甚至不想使用浮点数。无论对错,这些用途都占据了语言规范中很大的惯性。 - Stephen Canon
1
我不知道为什么那个最佳答案会上升到那里。它没有提供任何生成所需值的方法。而这个答案提供了。 - drysdam
#define is_nan(x) ((x) != (x)) 可以作为一个简单、便携的测试 NAN 的方法。 - Bob Stein
不是编译器切换陷阱,而是程序员。 我经常使用FPE陷阱来调试我的数值代码,并且刚刚遇到了一个使用类似方法的库。 结果我不能使用FPE陷阱,浪费了一个小时去捕捉一个如果该库的程序员使用了NAN宏就能在五分钟内捕捉到的错误。 - Roux

23
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);

6
这是一个智能便携式解决方案!C99需要使用strtod函数并能够转换NaN和Inf。 - ulidtko
2
并非这种解决方案没有缺点;它们不是常量。您不能使用这些值来初始化全局变量(或初始化数组)。 - Marc
1
@Marc。你可以始终拥有一个初始化函数,调用这些函数并将它们设置在全局命名空间中。这是一个非常可行的缺点。 - Mad Physicist

20

获取这些的方法不依赖于编译器,但是与处理器有关:

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

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

这应该适用于使用 IEEE 754 浮点格式的任何处理器(x86 使用该格式)。

更新:已测试并更新。


2
@WaffleMatt - 为什么这个在32/64位之间不能进行端口转换?无论底层处理器的寻址大小如何,IEEE 754单精度浮点数始终是32位。 - Aaron
6
(float &) 作为类型转换?这不像是 C 语言的写法。你需要使用int i = 0x7F800000; return *(float *)&i; 来进行操作。 - Chris Lutz
6
请注意,0x7f800001 是 IEEE-754 标准中所谓的“信号型” NaN。尽管大多数库和硬件不支持信号型 NaN,但最好返回一个静默 NaN,如 0x7fc00000 - Stephen Canon
6
警告:这可能会违反严格别名规则,引发未定义行为。在C语言中进行类型转换的推荐做法(也是编译器最好支持的方式)是使用联合体成员。请注意,翻译内容保持原意,通俗易懂,不涉及任何解释或不相关的信息。 - ulidtko
2
除了@ulidtko指出的严格别名问题之外,这还假定目标在整数和浮点数上使用相同的字节序,这绝对不是总是成立的。 - mr.stobbe
显示剩余9条评论

3
<inf.h>

/* IEEE positive infinity.  */

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

并且

<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.  */

0

这里有一种简单的方法来定义那些常量,我相信它是可移植的:

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

当我运行这段代码时:
printf("inf  = %f\n", inf);
printf("-inf = %f\n", -inf);
printf("nan  = %f\n", nan);
printf("-nan = %f\n", -nan);

我得到:
inf  = inf
-inf = -inf
nan  = -nan
-nan = nan

我查看了 /usr/include/math.h 来了解它们的作用。如果可用,它们使用内置值,但如果不可用,则似乎使用以下内容:code INFINITY = HUGE_VALF = 1e10000fcode NAN = (0.0f / 0.0f)无论如何,看起来 INFINITY 和 NAN 都被定义为固定常量。 - Patrick Chkoreff

0
在MSVC中,以下内容在correct_math.h中定义,在math.h中包含:
#ifndef _HUGE_ENUF
    #define _HUGE_ENUF  1e+300  // _HUGE_ENUF*_HUGE_ENUF must overflow
#endif

#define INFINITY   ((float)(_HUGE_ENUF * _HUGE_ENUF))
#define HUGE_VAL   ((double)INFINITY)
#define HUGE_VALF  ((float)INFINITY)
#define HUGE_VALL  ((long double)INFINITY)
#ifndef _UCRT_NEGATIVE_NAN
// This operation creates a negative NAN adding a - to make it positive
#define NAN        (-(float)(INFINITY * 0.0F))
#else
// Keep this for backwards compatibility
#define NAN        ((float)(INFINITY * 0.0F))
#endif

这并没有真正回答问题。如果您有不同的问题,可以点击提出问题进行提问。要在此问题获得新答案时接收通知,您可以关注此问题。一旦您拥有足够的声望,您还可以添加赏金以吸引更多关注。- 来自审核 - Emanuel P

-1

我通常使用

#define INFINITY (1e999)

或者

const double INFINITY = 1e999

这个方法至少在IEEE 754的上下文中有效,因为最高可表示的双精度浮点数大约是1e3081e309同样有效,1e99999也一样,但三个九已经足够且容易记忆。由于这要么是一个双精度字面量(在#define的情况下),要么是一个实际的Inf值,即使你使用128位(“长双精度”)浮点数,它仍将保持无穷。


2
在我看来,这非常危险。想象一下,有人在20年左右将你的代码迁移到128位浮点数上(在你的代码经历了不可预测的复杂演变过程之后)。突然,指数范围急剧增加,而你所有的1e999文本不再四舍五入为+Infinity。按照墨菲定律,这将破坏算法。更糟糕的是:做“128位”编译的人工程序员很可能事先没有注意到这个错误。也就是说,当发现和识别这个错误时,很可能已经太晚了。非常危险。 - ulidtko
1
当然,上述最坏情况可能远非现实。但是,请考虑其他选择!保守一点总是更好的。 - ulidtko
3
“20年后”,哈。得了吧,这个回答并不是那么糟糕。 - alecov
@ulidtko 我也不喜欢这个,但是真的吗? - Iharob Al Asimi

-1

我也很惊讶这些不是编译时常量。但我想你可以通过执行返回无效结果的指令来轻松创建这些值。比如除以0,log(0),tan(90)之类的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接