【发布时间】:2010-12-27 18:52:57
【问题描述】:
我有一个数值方法,如果出现错误,它可以返回 nan 或 inf,出于测试目的,我想暂时强制它返回 nan 或 inf 以确保情况得到正确处理。有没有一种可靠的、编译器无关的方法来在 C 中创建 nan 和 inf 的值?
在谷歌上搜索了大约 10 分钟后,我只能找到依赖于编译器的解决方案。
【问题讨论】:
-
浮点数不是由 C 标准定义的。所以没有独立于编译器的方式来做你想做的事。
我有一个数值方法,如果出现错误,它可以返回 nan 或 inf,出于测试目的,我想暂时强制它返回 nan 或 inf 以确保情况得到正确处理。有没有一种可靠的、编译器无关的方法来在 C 中创建 nan 和 inf 的值?
在谷歌上搜索了大约 10 分钟后,我只能找到依赖于编译器的解决方案。
【问题讨论】:
没有独立于编译器的方式来执行此操作,因为 C(或 C++)标准都没有规定浮点数学类型必须支持 NAN 或 INF。
编辑:我刚刚检查了 C++ 标准的措辞,它说这些函数(模板类 numeric_limits 的成员):
quiet_NaN()
signalling_NaN()
“如果可用”将返回 NAN 表示。它没有扩展“如果可用”的含义,但大概类似于“如果实现的 FP 代表支持它们”。同样,还有一个函数:
infinity()
“如果可用”返回正 INF 代表。
这些都在 <limits> 标头中定义 - 我猜 C 标准有类似的东西(可能也是“如果可用”),但我没有当前 C99 标准的副本。
【讨论】:
<math.h> 定义了 nan()、nanf() 和 nanl(),它们返回不同的 NaN 表示形式(作为 double、float 和 @987654330 @ 分别),并且无穷大(如果可用)可以通过使用log(0) 或其他东西生成一个来返回。即使在 C99 中,也没有检查它们的标准方法。不幸的是,<float.h> 标头(<limits.h> 用于整数类型)对 inf 和 nan 值保持沉默。
nanl() 返回 long double,而不是像我的评论所说的 int。我不知道为什么我在打字的时候没有意识到这一点。
一种独立于编译器的方式,但不是独立于处理器的方式来获得这些:
int inf = 0x7F800000;
return *(float*)&inf;
int nan = 0x7F800001;
return *(float*)&nan;
这应该适用于任何使用 IEEE 754 浮点格式(x86 可以)的处理器。
更新:经过测试和更新。
【讨论】:
(float &) ?在我看来,这不像 C。你需要int i = 0x7F800000; return *(float *)&i;
0x7f800001 是 IEEE-754 标准中所谓的 signaling NaN。尽管大多数库和硬件不支持信号 NaN,但最好返回一个安静的 NaN,例如 0x7fc00000。
我也很惊讶这些不是编译时常量。但是我想您可以通过简单地执行返回这种无效结果的指令来轻松地创建这些值。除以 0、0 的对数、90 的 tan 等等。
【讨论】:
你可以测试你的实现是否有它:
#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)。
【讨论】:
a != a 应该永远被使用。
a 不是数字时,a == NAN 返回 false。 IEEE 需要它。即使是遵循 IEEE 的实现,大部分也是如此。当isnan()没有实现时,还是直接封装测试比直接编码a == NAN好。
a != a 在两个不同的优化级别下也可能表现不同。谨慎使用此比较!
这适用于float 和double:
double NAN = 0.0/0.0;
double POS_INF = 1.0 /0.0;
double NEG_INF = -1.0/0.0;
编辑: 正如有人已经说过的那样,旧的 IEEE 标准说 这样的价值观应该引发陷阱。但是新的编译器 几乎总是关闭陷阱并返回 给定值,因为陷印会干扰错误 处理。
【讨论】:
#define is_nan(x) ((x) != (x)) 作为一个简单的、可移植的 NAN 测试可能很有用。
<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. */
【讨论】:
我经常用
#define INFINITY (1e999)
或
const double INFINITY = 1e999
至少在 IEEE 754 上下文中有效,因为最高可表示的双精度值大约是 1e308。 1e309 和 1e99999 一样有效,但三个 9 就足够且令人难忘。由于这是一个双精度字面量(在 #define 的情况下)或一个实际的 Inf 值,因此即使您使用 128 位(“long double”)浮点数,它也将保持无限。
【讨论】:
1e999 文字不再四舍五入为+Infinity。根据墨菲定律,这破坏了算法。更糟糕的是:执行“128 位”构建的人类程序员不太可能提前发现该错误。 IE。很可能为时已晚发现并识别此错误。非常危险。
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
【讨论】:
strtod 并转换 NaN 和 Inf。
这是定义这些常量的简单方法,我很确定它是可移植的:
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
【讨论】:
code INFINITY = HUGE_VALF = 1e10000f code NAN = (0.0f / 0.0f) 在任何情况下看起来 INFINITY 和 NAN 定义为固体常数。