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

What is the purpose of NaN instead of abort in C floating-point?

我从 GNU 科学图书馆得到了一个计算结果。看起来像这样

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

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

From the language design perspective, what is the purpose of having "nan" silently, rather than abort? Efficiency?

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

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

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

try {
    a = f(4.0);
} catch (NaNException) {

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

这个java代码:

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

对应这个C代码:

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

请注意,您必须在 之前进行检查,这比事后进行检查要棘手得多。在这个简单的例子中,这是微不足道的。但是考虑一个稍微不那么简单的例子。想象一下,您有一个执行总共 20 个除法的函数。那么您可能需要 20 个 if 语句来解决这个问题。我们也有上面提到的情况,您无法访问源代码,只能完全相信创建库的人。

NaN means the involved calculation is meaningless or totally erroneous, right?

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

而 C 中的错误处理基本上是由程序员进行适当的错误检查。

实际上,您可以使用 NAN 作为明确的错误指示符。示例:

double f(double arg) {
    if(arg < 0)
        return NAN;
    ...
}

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

if(f(a) == NAN) {

改为使用:

if(isnan(f(a))) {