如何在C89中检查NaN和inf

6
在C99之前的规范中,isnan(),isinf()函数并不存在。那么在C89中有没有实现这样的功能的方法呢?可以使用if (d * 0 != 0)来检查d是否为NaN或Inf,但我们总是使用选项-Werror=float-equal编译我们的项目,这会提示错误:error: no == or != on float point value。那么人们如何在C89中检查NaN和Inf呢?

2
尝试使用 x > DBL_MAX 表示无穷大,使用 x != x 表示 NAN。 - chux - Reinstate Monica
1
C99已经发布20多年了。为什么对C89还有兴趣? - chux - Reinstate Monica
1
@chux 我认为你的第一条评论可以成为一个有趣问题的好答案,只需要稍加解释。我自己无法提供一个好的解释,所以能否请你写一个答案呢? - Yunnosch
2
@chux-ReinstateMonica 直到大约5年前,我一直被专业限制在C89中。这是一个目标导向的资格和许可证问题,与稍微不寻常的环境中使用的编译器有关。还有一些担心人们所依赖的代码行为。所有这些都带有一丝不完全是不合理的偏执症。 - Yunnosch
@Yunnosch,@chux的第一条评论可能无法工作,因为编译选项-Werror=float-equal - hukeping
显示剩余4条评论
4个回答

7

自C99起

isinf(x)isnan(x)适用于x实浮点类型,对于其他类型如int则未定义。 isinf(x)isnan(x)是宏定义。

isinf(x)isnan(x)float,double,long double使用相同的名称,并且就像重载函数一样。

isinf

对于C89, 我们可以使用单独的函数来测试_MAX。请注意,C89未定义long double

"自制"的isinf()可以使用以下内容。

#include <float.h>
int isinf_f(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int isinf_d(double x) { return x < -DBL_MAX || x > DBL_MAX; }

请注意,C语言不要求实现支持无穷大。如果支持,则上述内容从来不成立。
isnan
对于C89,自己实现一个“isnan()”函数更加棘手。它可以作为函数或简单的宏来完成。功能依赖于最新的非数字行为,其中NaN永远不等于任何东西,甚至不等于它本身。这不是C89规定的,但通常底层浮点系统遵循这个规则。否则,您需要采用更具平台特定性的方法。
/* Note `x` used twice here - so use with caution */
#define my_is_nan(x) ( (x) != (x) )

int isnan_f(float x) { x != x; }
int isnan_d(double x) { x != x; }

注意,C语言不要求实现支持“非数字”(Not-a-number)。如果不支持,“以上内容”将从未成立。
考虑到C89时代的无序状态,我不会假设符合IEEE 754。无限(Infinity)和NAN是任何浮点实现的边界,它们缺乏正式的兼容性。祝好运。
C允许使用FP更广泛的数学,具体取决于FLT_EVAL_METHOD,因此1.0f / 7.0f可能使用double。这使得事情有些复杂,但使用真正的函数确实能将x表达式强制转换为所需类型。

2

如果您的系统使用 IEEE 754标准来表示浮点数值(大多数情况下),那么您可以明确检查NaNinf值。单精度(32位)浮点值具有1 符号位(第31位),8位 指数(位30-23)和23位 尾数(位22-0),排列如下(以二进制格式):

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

一个无穷大的值用所有1的指数和所有0的尾数表示(符号位区分负无穷大和正无穷大)。

NaN值由所有1的指数和非零分数表示(quiet NaN具有最高有效尾数位设置,而signalling NaN则清除此位)。

因此,通过将float强制转换为无符号32位整数,我们可以明确地检查这些表示(假设unsigned int是32位类型):

int IsInfinite(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) == 0 );
}

int NotANumber(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) != 0 );
}

双精度值的表示方式类似,但指数位有11个(62-52),尾数位有52个(51-0)。

11
unsigned int mask = *(unsigned int *)(&test); 违反了 strict aliasing 规则,忽略了任何可能的字节序问题,并假设浮点数 float 的所有位都恰好适合于一个 unsigned int - Andrew Henle
@AndrewHenle,您的意思是如果我们的硬件以大端形式表示浮点数,这两个函数就无法正常工作? - hukeping
@hukeping:当浮点数的字节序与“int”不同时,该方法会失败。这在真实硬件上不会发生——它会破坏IEEE754背后的基本思想,例如按其二进制表示对数字进行排序。 - MSalters

1

经过审核,C89也许可以采用另一种方法。

如果我们假设将NaN与有限值进行比较始终为false(这是IEEE的方法),我们可以猜测在不遵循该方法的系统中,比较始终为true(而不是有时为true)。然后,关键是处理这两种情况。

对于非NaN的x(x >= 0.0) == (x < 0.0)应该总是false,因为从数学上讲它们是相反的测试。

对于NaN的x,如果我们幸运地发现这样的x与常量的比较始终为false或始终为true,则is_nan()的结果为true。

// Assuming -Werror=float-equal 
int is_nan(double x) {
  return (x >= 0.0) == (x < 0.0);
}

尽可能地,我还会为C89编译器插入一个static_assert()。由于C89中不存在_Static_assert,因此请考虑使用此处找到的各种C89替代品。

-2

我想尝试通过逻辑推理来揭示NaN

如果一个float既不是无穷大(既不大于FLT_MAX也不小于-FLT_MAX),那么它应该小于或等于FLT_MAX。如果不是,那么它就是NaN

int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

这是我的第一个回答,却已经被踩了。所以我决定加上一个例子。好吧,它按预期工作。

#include <stdio.h>
#include <math.h>
#include <float.h>

static int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
static int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

static void show(float f)
{
  float g = -f;
  printf("% f : %i %i %i %i\n", f, isinf(f), isnan(f), is_inf(f), is_nan(f));
  printf("% f : %i %i %i %i\n", g, isinf(g), isnan(g), is_inf(g), is_nan(g));
}

int main(void)
{
  float inf = FLT_MAX * 2.0f;
  float nan = inf / inf;

  show(inf);
  show(nan);
  show(FLT_MAX);
  show(FLT_MIN);
  show(0.0f);
  show(1234.5678f);

  return 0;
}

在Linux下编译并启动:

$ gcc infnan.c
$ ./a.out
 inf : 1 0 1 0
-inf : -1 0 1 0
-nan : 0 1 0 1
 nan : 0 1 0 1
 340282346638528859811704183484516925440.000000 : 0 0 0 0
-340282346638528859811704183484516925440.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 1234.567749 : 0 0 0 0
-1234.567749 : 0 0 0 0

提供对数据类型double的补充解决方案,改进了返回值。

static int is_inf(double x) {
  if (x >  DBL_MAX) return  1;
  if (x < -DBL_MAX) return -1;
  return 0;
}
static int is_nan(double x) { return !is_inf(x) && !(x <= DBL_MAX); }

重新思考一下,is_nan 可以更容易地计算。
int is_nan(double x) { return !(x > DBL_MAX || x <= DBL_MAX); }

或者甚至可以不包括常量。

int is_nan(double x) { return !(x > 0.0 || x <= 0.0); }

1
nan 不等于、小于或大于任何东西。 - S.S. Anne
@S.S.Anne “nan不与任何东西相等、小于或大于”在C89中没有明确规定。 这是IEEE 754规范。这是一个很好的开始,但由于OP需要一个C89解决方案,因此更有可能“nan”不遵循今天(2020年)的预期实践。 OP可能需要针对特定实现的解决方案。 - chux - Reinstate Monica
Andreas,Minor:建议使用"%-14g"代替"%f"以获得更干净、更具信息性的表格。 - chux - Reinstate Monica
@S.S.Anne: 特别是对于x为NaN(仅对NaN),(x<C)==(x>=C)(两者都为false)。这个建议碰巧使用C=FLT_MAX作为要比较的常量,这很好。至于非IEEE754答案,那并没有太多意义。NaN非常符合IEEE754的发明,尤其是非信号NaN。 - MSalters

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