在 C 浮点中使用 NaN 而不是 abort 的目的是什么?

zel*_*ell 1 c floating-point

我从 GNU Scientific Library 得到了一个计算结果。看起来像这样

iter  0: A = 1.0000, lambda = 1.0000, b = 0.0000, cond(J) =      inf, |f(x)| = 101.0200
iter  1: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =  92.8216, |f(x)| = -nan
iter  2: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  3: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  4: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  5: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  6: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  7: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  8: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter  9: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 10: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 11: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 12: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 13: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 14: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 15: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 16: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 17: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 18: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 19: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
iter 20: A = 3.5110, lambda = -12.8820, b = 1.2364, cond(J) =      nan, |f(x)| = -nan
Run Code Online (Sandbox Code Playgroud)

因此,您可以cond(J) = nan, |f(x)| = -nan从第二次迭代中看到。从语言设计的角度来看,让“nan”默默地而不是中止的目的是什么?效率?

klu*_*utt 5

从语言设计的角度来看,让“nan”无声无息,而不是中止的目的是什么?效率?

效率是原因之一。但另一个是 C 缺乏异常的概念。考虑以下。您正在使用预编译的库,您无法访问源代码。你只有头文件和目标文件,所以你有一个double f(double arg)在头文件中声明并在目标文件中定义的函数。

那么如果你不想让程序崩溃怎么办?然后,您必须确保该函数甚至不会被调用,如果arg是一个f将返回的值NaN。这确实非常棘手。特别是在这种情况下,您甚至无法访问源代码。

在 Java 中,计算 NaN 时不会抛出异常。但如果是这样,你会做这样的事情:

try {
    a = f(4.0);
} catch (NaNException) {
Run Code Online (Sandbox Code Playgroud)

你会如何在 C 中做到这一点?当涉及到整数除以零时,这实际上是 C 中的一个问题。这会在 C 中调用未定义的行为,没有机会恢复。

这个java代码:

try {
    a = 100/d;
} catch (ArithmeticExpression) {
    System.out.println("<d> must not be zero");
Run Code Online (Sandbox Code Playgroud)

对应于这个 C 代码:

if(d == 0) {
    puts("<d> must not be zero");
} else {
    a = 100/d;
Run Code Online (Sandbox Code Playgroud)

请注意,您必须在之前进行检查,这可能比之后进行检查要棘手得多。在这个简单的情况下,它是微不足道的。但是考虑一个稍微不那么琐碎的例子。假设您有一个总共执行 20 个除法的函数。那么你可能需要 20 个 if 语句来覆盖这个。我们也有上面提到的情况,在这种情况下,您无法访问源代码,而只能依靠对创建库的人的纯粹信任。

NaN 意味着所涉及的计算毫无意义或完全错误,对吗?

并不真地。代码可能完全没问题,错误可能是代码输入错误。这并不一定意味着程序需要崩溃。

C 中的错误处理基本上是由程序员来进行正确的错误检查。

实际上,您可以将其NAN用作显式错误指示器。例子:

double f(double arg) {
    if(arg < 0)
        return NAN;
    ...
}
Run Code Online (Sandbox Code Playgroud)

但要小心,因为这行不通:

if(f(a) == NAN) {
Run Code Online (Sandbox Code Playgroud)

相反,使用这个:

if(isnan(f(a))) {
Run Code Online (Sandbox Code Playgroud)