如何在C中生成NaN浮点数?

32
float f = (float)'a';
if(f < 0){ 
}   
else if(f == 0){ 
}   
else if(f > 0){ 
}   
else{
    printf("NaN\n");                                                          
}   

如果fNaN,则它不会大于/等于/小于0

但是如何首先产生这样的f呢?

我尝试了各种方法来产生NaN,但都无法成功。


1
http://www.gnu.org/s/hello/manual/libc/Infinity-and-NaN.html - Matt Ball
1
你会用一点C++吗?C++有std::numeric_limits的东西,其中包括安静和信号NaN的常量。另外,你确定你的系统正确支持NaN吗?因为当你说0.0/0.0不是NaN时,我真的很惊讶,开始怀疑你的库没有按照你想象的那样设置。 - Michael Kohne
这里我展示了不同方式生成的各种NaN的样子:https://dev59.com/rWMl5IYBdhLWcg3wzpjV#55648118 C++版本:https://dev59.com/7GQn5IYBdhLWcg3wn4RS - Ciro Santilli OurBigBook.com
9个回答

28

使用浮点数,0.0 / 0.0 不是 "除以零" 错误;它会得到 NaN

这个 C 程序会打印出 -nan

#include <stdio.h>

int main()
{
    float x = 0.0 / 0.0;
    printf("%f\n", x);
    return 0;
}

就NaN在计算机中的表现而言,有两个“无效”数字用于“信令”和“安静”的NaN(类似于保留给正无穷大和负无穷大的两个无效数字)。维基百科条目中有关于NaN如何表示为IEEE浮点数的更多细节。


1
@Matt Ball,这是否意味着IEEE包含对小于2^128浮点值的定义?只有在这种情况下,才可以使用一些特殊值作为NaN。 - Je Rog
2
@Dan Cecile:我认为除以零会产生一个未定数(IND),而不是NaN。 - MNS
2
@MNS 整数除以零是未定义行为。在C语言中不存在“不定数”或“IND”。 - Pascal Cuoq
Visual Studio(我使用的是2015版本)仍然将此视为编译时错误C2124,因此不具备可移植性。 - Kit10
1
@Copperpot 是的,微软编译器的问题是众所周知的。请参见我的回答(我刚刚发布)以获取这些编译器(由GNU MPFR使用)的解决方案以及其他各种实现的解决方案,每次都提到可移植性问题。 - vinc17
显示剩余8条评论

26

产生NaN有几种方法:

1)手动生成(通过阅读ieee754以正确设置位数)

2)使用宏。GCC提供了一个名为NAN的宏,它在math.h中定义。

检查NaN的通用方法是检查if (f == f)(对于NaN值应该失败)

对于NaN,浮点表示中的指数位应全部设置为1(浮点由一个带符号位、一组指数位和一组尾数位组成)


5
你也可以使用isnan()来检查NaN(需要C99或(在Unix系统上)适当的特性标志,详见manpage)。 - bdonlan
1
@bdonlan 稍微离题,但实际上很奇怪的是Python直到2.6才引入isnan函数。 - Foo Bah
我在我的工具集中的bits/nan.h中发现了NAN。 - DarenW
对于(1),IEEE 754没有涵盖各种可移植性问题(有关详细信息,请参见我的答案)。关于(2),NAN并不来自GCC,而是来自C库。例如,可以查看GNU libc的.../bits/nan.h文件(由@DarenW提到);但是,当通过预处理器测试检测到时,宏定义是特定于GCC的。 - vinc17
对于 NaN,浮点表示中的指数位应全部设置为 1 - 这对于 naninf 都是正确的。 - Matt

14

您可以使用NAN宏,或者直接使用nan / nanf函数将NaN值赋给变量。
如果要检查是否处理了NaN值,则可以使用isnan()函数。
以下是一个例子:

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

int main(void) {

    float a = NAN;//using the macro in math.h
    float f = nanf("");//using the function version 
    double d = nan("");//same as above but for doubles!

    printf("a = %f\nf = %f\nd = %f\n",a,f,d);

    if(isnan(a))
        puts("a is a not a number!(NAN)\n");

    return 0;
}

运行上面的代码片段会得到以下输出:

a = nan
f = nan
d = nan
a is a not a number!(NAN)

自行运行代码: http://ideone.com/WWZBl8
阅读更多信息:http://www.cplusplus.com/reference/cmath/NAN/


可能是因为nannanf不是标准的C函数。 - skyking
10
自 1999 年以来,@skyking 中的 nannanf 函数已成为标准的 ISO C 函数。 - MarkWeston

3
从GNU GCC手册中可以看出,math.h定义了一些宏,允许您将变量显式设置为无穷大或NaN。由于这是C99的一部分,您可以在其他符合c99标准的编译器中使用以下宏。
— 宏:float INFINITY 表示正无穷大的表达式。 它等于数学运算(如1.0 / 0.0)产生的值。 -INFINITY表示负无穷大。
您可以通过将其与此宏进行比较来测试浮点值是否为无穷大。但是,不建议使用此方法;您应该改用isfinite宏。 请参见浮点类。
此宏是在ISO C99标准中引入的。
— 宏:float NAN 表示“非数字”值的表达式。 此宏是GNU扩展,仅在支持“非数字”值(即在支持IEEE浮点数的所有机器上)的机器上可用。
您可以使用“#ifdef NAN”来测试计算机是否支持NaN。(当然,您必须安排GNU扩展可见,例如通过定义_GNU_SOURCE,然后必须包括math.h。)
有关更多信息,请参见以下链接:http://www.gnu.org/s/hello/manual/libc/Infinity-and-NaN.html

NAN宏不是GNU扩展(至少不再是)。它已经被引入到ISO C99标准中。因此,不需要定义_GNU_SOURCE,只需在某种形式的C99模式或更高版本(而不是C89)下编译即可。 - vinc17

3
对于托管的C实现,可以使用#include <math.h>并使用NAN宏(如果已定义)。例如,对于GCC,它是通过内置函数实现的:(__builtin_nanf(""))
对于自由C实现(可能没有可用的<math.h>头文件)或当NAN宏未定义时(即使支持NaN,也可能发生),可以通过浮点运算生成NaN,例如0.0 / 0.0。但是,这可能会有几个问题。
首先,这样的操作还会生成异常,在某些C实现中可能会触发陷阱。可以通过以下方式确保在编译时计算它:
static double my_nan = 0.0 / 0.0;

另一个问题是Microsoft Visual C++(至少某些版本)试图在编译时计算0.0 / 0.0,即使此表达式在代码的任意位置,也会抱怨其有效性。所以,这里的解决方案是相反的:确保编译器不会在编译时对其进行评估,方法是执行以下操作:

static double zero = 0.0;

然后使用zero / zero。由于这些解决方案冲突,可以在特定的宏上测试编译器的预处理器指令(#if...)。
也可以选择基于NaN编码的解决方案,但也存在可移植性问题。首先,IEEE 754标准并没有完全定义NaN的编码方式,特别是区分quiet和signaling NaNs的方式(实际上硬件有所不同);signaling NaN会产生未定义行为。此外,IEEE 754标准没有定义位字符串在内存中的表示方式,即可能需要检测尾数。如果解决了这些问题,使用unsigned char的联合或数组和指针转换就可以得到浮点类型。不要使用整数和其地址的指针转换来进行类型游戏,因为这将违反C别名规则。

2

将浮点变量的所有32位设置为1也可以产生-nan,如下所示:

float nan_val = 0xffffffff;

此外,您可以通过检查自身的比较是否失败来明确比较浮点变量是否为-nan
if (nan_val != nan_val) {
// executes iff nan_val is -nan
}

这种比较方法适用于使用IEEE浮点数的编译器。


4
"float nan_val = 0xffffffff;" 只是创建了一个值为4294967296的浮点数,而不是负无穷大。 - rsaxvc

0
这对常量也适用(0/0将在vs上产生编译器错误):
const unsigned maxU = ~0;
const float qNan =  *((float*)&maxU);

1
至少有四个可移植性问题:有符号整数类型上的~int 可能太短(例如在某些嵌入式系统上),NaN 表示和破损的别名规则。请参见我的回答了解更多细节。 - vinc17

0
以下C程序将生成NaN。第二个语句将导致NaN。
#include <stdio.h>
#include <tchar.h>
#include "math.h"

int _tmain(int argc, _TCHAR* argv[])
{
    double dSQRTValue = sqrt( -1.00 ); 
    double dResult = -dSQRTValue;  // This statement will result in a NaN.
    printf( "\n %lf", dResult );

    return 0;
}

以下是程序的输出结果。

1.#QNAN0


sqrt( -1.00 );
这在一些机器上会出错。
- rsaxvc
@rsaxvc C标准库的sqrt()函数规定在给定负数参数时返回NaN。这种行为与IEEE 754浮点标准一致,该标准被现代处理器广泛支持。另一方面,非标准的浮点实现可能会以不同的方式处理这种情况,并且在输入为负数时可能会生成错误。 - MNS
你说得对!我的符合IEEE标准的平台并不符合C语言标准。 - rsaxvc

-3

当我们的程序包含像0.0/0.0或sqrt(-1)这样的值时,就会产生NaN错误。


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