如何在C中使用nan和inf?

Posted

技术标签:

【中文标题】如何在C中使用nan和inf?【英文标题】:How to use nan and inf in C? 【发布时间】:2010-12-27 18:52:57 【问题描述】:

我有一个数值方法,如果出现错误,它可以返回 nan 或 inf,出于测试目的,我想暂时强制它返回 nan 或 inf 以确保情况得到正确处理。有没有一种可靠的、编译器无关的方法来在 C 中创建 nan 和 inf 的值?

在谷歌上搜索了大约 10 分钟后,我只能找到依赖于编译器的解决方案。

【问题讨论】:

浮点数不是由 C 标准定义的。所以没有独立于编译器的方式来做你想做的事。 【参考方案1】:

没有独立于编译器的方式来执行此操作,因为 C(或 C++)标准都没有规定浮点数学类型必须支持 NAN 或 INF。

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

quiet_NaN() 
signalling_NaN()

“如果可用”将返回 NAN 表示。它没有扩展“如果可用”的含义,但大概类似于“如果实现的 FP 代表支持它们”。同样,还有一个函数:

infinity() 

“如果可用”返回正 INF 代表。

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

【讨论】:

这既令人失望又令人惊讶。 C 和 C++ 不符合 IEEE 浮点数,它对 nan 和 inf 有标准表示? 在 C99 中,C 标头 <math.h> 定义了 nan()nanf()nanl(),它们返回不同的 NaN 表示形式(作为 doublefloat 和 @987654330 @ 分别),并且无穷大(如果可用)可以通过使用log(0) 或其他东西生成一个来返回。即使在 C99 中,也没有检查它们的标准方法。不幸的是,<float.h> 标头(<limits.h> 用于整数类型)对 infnan 值保持沉默。 哇,这是一个很大的混淆。 nanl() 返回 long double,而不是像我的评论所说的 int。我不知道为什么我在打字的时候没有意识到这一点。 @Chris,请参阅我对 C99 的回答。 @IngeHenriksen - 很确定微软已经声明它无意让 VC++ 支持 C99。【参考方案2】:

一种独立于编译器的方式,但不是独立于处理器的方式来获得这些:

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

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

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

更新:经过测试和更新。

【讨论】:

@WaffleMatt - 为什么这个端口不在 32/64 位之间?无论底层处理器的寻址大小如何,IEEE 754 单精度浮点数都是 32 位的。 投射到(float &) ?在我看来,这不像 C。你需要int i = 0x7F800000; return *(float *)&i; 请注意,0x7f800001 是 IEEE-754 标准中所谓的 signaling NaN。尽管大多数库和硬件不支持信号 NaN,但最好返回一个安静的 NaN,例如 0x7fc00000 警告:这可能会通过违反strict aliasing 规则触发未定义行为。 type punning 的推荐(并且在编译器中得到最好的支持)方法是通过 union members。 除了@ulidtko 指出的严格别名问题之外,这还假设目标使用与浮点相同的整数字节序,但情况并非总是如此。【参考方案3】:

我也很惊讶这些不是编译时常量。但是我想您可以通过简单地执行返回这种无效结果的指令来轻松地创建这些值。除以 0、0 的对数、90 的 tan 等等。

【讨论】:

【参考方案4】:

你可以测试你的实现是否有它:

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

INFINITY 的存在由 C99(或至少最新草案)保证,并且“扩展为浮点类型的常量表达式,表示正数或无符号数 无穷大,如果有的话;否则为在转换时溢出的浮点类型的正常量。”

NAN 可以定义也可以不定义,并且“当且仅当实现支持浮点类型的安静 NaN 时才定义。它扩展为表示安静 NaN 的浮点类型的常量表达式。”

请注意,如果您要比较浮点值,请执行以下操作:

a = NAN;

即便如此,

a == NAN;

是假的。检查 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)。

Docs INFINITY Docs NAN

【讨论】:

优秀的答案。 NAN 和 INFINITY 宏的参考是 C99 §7.12 第 4 和 5 段。除 (isnan(a)) 外,您还可以在符合 C 的实现上使用 (a != a) 检查 NaN。 为了便于阅读,a != a 应该永远被使用。 @ChrisKerekes:可悲的是,我们中的一些人有 NAN,但没有 isnan()。是的,这是 2017 年。:( C 不需要,当 a 不是数字时,a == NAN 返回 false。 IEEE 需要它。即使是遵循 IEEE 的实现,大部分也是如此。当isnan()没有实现时,还是直接封装测试比直接编码a == NAN好。 补充一下 chux 所说的,即使在具有相同编译器的 IEEE 754 系统上,a != a 在两个不同的优化级别下也可能表现不同。谨慎使用此比较!【参考方案5】:

这适用于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 中得到了极大的澄清。 嗨,斯蒂芬,你是对的,但标准还说:“用户应该能够通过为其指定处理程序来请求对五个异常中的任何一个进行捕获。他应该能够请求禁用、保存或恢复现有处理程序。他还应该能够确定是否已启用指定异常的特定陷阱处理程序。定义的“应该”(2.定义)意味着“强烈推荐”,并且只有在架构等使其不切实际时才应省略其实现。 80x86 完全支持该标准,因此 C 没有理由不支持它。 我同意 C 应该要求 754 (2008) 浮点数,但有充分的理由不这样做;具体来说,C 用于 x86 以外的各种环境——包括没有硬件浮点的嵌入式设备,以及程序员甚至不想使用浮点的信号处理设备。不管是对是错,这些用法在语言规范中造成了很大的惯性。 我不知道为什么最佳答案在那里出现。它没有提供任何生成请求值的方法。这个答案可以。 #define is_nan(x) ((x) != (x)) 作为一个简单的、可移植的 NAN 测试可能很有用。【参考方案6】:
<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.  */

【讨论】:

【参考方案7】:

我经常用

#define INFINITY (1e999)

const double INFINITY = 1e999

至少在 IEEE 754 上下文中有效,因为最高可表示的双精度值大约是 1e3081e3091e99999 一样有效,但三个 9 就足够且令人难忘。由于这是一个双精度字面量(在 #define 的情况下)或一个实际的 Inf 值,因此即使您使用 128 位(“long double”)浮点数,它也将保持无限。

【讨论】:

在我看来,这非常危险。想象一下有人如何在 20 年左右的时间里将您的代码迁移到 128 位浮点数(在您的代码经历了令人难以置信的复杂演变之后,您今天无法预测的阶段)。突然,指数范围急剧增加,所有1e999 文字不再四舍五入为+Infinity。根据墨菲定律,这破坏了算法。更糟糕的是:执行“128 位”构建的人类程序员不太可能提前发现该错误。 IE。很可能为时已晚发现并识别此错误。非常危险。 当然,上述最坏的情况可能远非现实。但是,请考虑替代方案!最好保持安全。 “20年后”,呵呵。快点。这个答案还不错。 @ulidtko 我也不喜欢这个,但真的吗?【参考方案8】:
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);

【讨论】:

这是一款智能便携解决方案! C99 需要 strtod 并转换 NaN 和 Inf。 并不是说这个解决方案有缺点;它们不是常数。例如,您不能使用这些值来初始化全局变量(或初始化数组)。 @Marc。你总是可以有一个初始化函数,调用一次并将它们设置在全局命名空间中。这是一个非常可行的缺点。【参考方案9】:

这是定义这些常量的简单方法,我很确定它是可移植的:

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 = 1e10000f code NAN = (0.0f / 0.0f) 在任何情况下看起来 INFINITY 和 NAN 定义为固体常数。

以上是关于如何在C中使用nan和inf?的主要内容,如果未能解决你的问题,请参考以下文章

如何在双变量中检查 inf(和 | 或)NaN

C语言中INF和NAN是啥意思

C语言中nan的使用

IEEE754 浮点数系统中的 NaN 和 +-INF

如何消除“外部函数调用中的 NA/NaN/Inf (arg 7)”使用 randomForest 运行预测

如果遇到非有限值(NA、NaN 或 Inf),如何强制出错