如何将 long double 与 qsort 以及 NaN 进行比较?

How to compare long doubles with qsort and with regard to NaN?

How to compare long doubles with qsort() and with regard to not-a-number?

在对可能包含非数字的数组进行排序时,我想将所有这些 NAN 放到已排序数组的一端。


qsort() 对比较函数施加了一些限制。

The function shall return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
C11dr §7.22.5.2 3

When the same objects ... are passed more than once to the comparison function, the results shall be consistent with one another. That is, for qsort they shall define a total ordering on the array, ... the same object shall always compare the same way with the key.
§7.22.5 4

a <= ba 不是数字或 b 不是数字时,

a > b 为假。因此 a > b!(a <= b) 不同,因为如果其中之一为 NaN,则结果相反。

如果比较函数使用 return (a > b) - (a < b);,如果 ab 之一或两者为 NaN,代码将 return 0。该数组不会按需要排序,它失去了 总排序 要求。

在使用 int isnan(real-floating x);int isfinite(real-floating x); 等分类函数时,这种排序的 long double 方面很重要。我知道 isfinite( finite_long_double_more_than_DBL_MAX) 可能 return 错误。所以我担心 isnan(some_long_double) 可能会做出 某些 意外的事情。


我尝试了以下方法。它显然按需要排序。

子问题:下面的compare()是否足以按需要排序?任何推荐的简化?如果不是 - 如何解决? (对于此任务,0.0L 和 -0.0L 之类的值可以以任何方式排序)

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

int compare(const void *a, const void *b) {
  const long double *fa = (const long double *) a;
  const long double *fb = (const long double *) b;
  if (*fa > *fb) return 1;
  if (*fa < *fb) return -1;

  if (*fa == *fb) {
    //return -memcmp(fa, fb, sizeof *fa); if -0.0, 0.0 order important.
    return 0;
  }
  // At least one of *fa or *fb is NaN
  // is *fa a non-NaN?
  if (!isnan(*fa)) return -1;
  if (!isnan(*fb)) return 1;

  // both NaN
  return 0;
  // return -memcmp(fa, fb, tbd size); if NaN order important.
}

int main(void) {
  long double x[] = { 0.0L / 0.0, 0.0L / 0.0, 0.0, 1.0L / 0.0, -0.0, LDBL_MIN,
      LDBL_MAX, 42.0, -1.0L / 0.0, 867-5309, -0.0 };
  x[0] = -x[0];
  printf("unsorted: ");
  size_t n = sizeof x / sizeof x[0];
  for (size_t i = 0; i < n; i++) {
    printf("%.3Le,", x[i]);
  }
  printf("\nsorted: ");
  qsort(x, n, sizeof x[0], compare);
  for (size_t i = 0; i < n; i++) {
    printf("%.3Le,", x[i]);
  }
  puts("");
}

输出

unsorted: nan,-nan,0.000e+00,inf,-0.000e+00,3.362e-4932,1.190e+4932,4.200e+01,-inf,-4.442e+03,-0.000e+00,
sorted: -inf,-4.442e+03,-0.000e+00,0.000e+00,-0.000e+00,3.362e-4932,4.200e+01,1.190e+4932,inf,nan,-nan,

如果我知道比较功能是正确的,我会 post 进行代码审查以获得改进想法。然而,我对代码能正确处理那些讨厌的 NaN 没有足够的信心。

这只是对您的测试进行简单的重新排序,但如果您愿意,它会使 NaN 的状态更加清晰。

int compare(const void *a, const void *b)
{
    const long double fa = *(const long double *) a;
    const long double fb = *(const long double *) b;

    if (isnan(fa))
    {
        if (isnan(fb))
        {
            return 0;
        }
        return 1;
    }
    if (isnan(fb))
    {
        return -1;
    }
    if (fa > fb) return 1;
    if (fa < fb) return -1;

    /* no more comparisons needed */
    return 0;
}

由于 NaN 的测试在顶部并且不应通过任何 NaN,因此可以安全地用您的

替换底部三行
return (a > b) - (a < b);

除了讨论 NaN 的不同 类型 (听起来有点像有多少天使可以在 CPU 核心上跳舞),这个对于您的目的应该足够稳定,我看不出这段代码有任何可能的问题。

使用 Clang,-ffast-math-fdenormal-fp-math=[ieee|preserve-sign|positive-zero] 都不会产生其他结果。 -ffast-math 也没有 gcc, -funsafe-math-optimizations,甚至 -ffinite-math-only(后者最有可能是因为除了与 NaN 的直接比较之外没有其他操作)。

为了完整起见,我还用 std::numeric_limits<double>::signaling_NaN();std::numeric_limits<double>::quiet_NaN();(来自 C++ <limits.h>)进行了测试——同样,排序顺序没有区别。

NaN测试

int isnan(real-floating x);

The isnan macro determines whether its argument value is a NaN. First, an argument represented in a format wider than its semantic type is converted to its semantic type. Then determination is based on the type of the argument.235
235 For the isnan macro, the type for determination does not matter unless the implementation supports NaNs in the evaluation type but not in the semantic type.

isnan(some_long_double) 将按预期工作,但在罕见的平台上除外。

int isunordered(real-floating x, real-floating y) 表现得像 isnan() 希望它能解释两个参数。

许多平台上,代码可以使用(a == a)作为候选NaN测试,因为当a是NaN并且1 否则。不幸的是,除非实现定义了 __STDC_IEC_559__,否则它不一定有效。


比较
>=, >, <, <= 和 C11 7.12.14 比较宏

当至少一个操作数是 NaN 时使用 >=, >, <, <= 会导致 "invalid" 浮点异常。因此,正如

所回答的那样,事先对 NaN 进行测试是谨慎的

C 提供宏 isgreaterequal(), isgreaterequal(), isless(), islessthna() 进行比较而不引发 "invalid" 浮点数 例外。这是 double 的一个很好的替代方法,但宏使用 真实浮动 ,这可能与 long double 不同。 isgreater(long_double_a, long_double_a) 可能会评估为 double 而不会提供所需的比较结果。

分类宏的挑战在于 语义类型 可能比 long double.

更窄

以下使用上述想法,当我阅读 C 规范时,它定义明确并且在功能上对所有情况都是正确的,除了罕见的情况:当 long double 有 NaN 而不是 real-浮动(通常double)不会。

#include <math.h>

// compare 2 long double.  All NaN are greater than numbers.
int compare(const void *a, const void *b) {
  const long double *fa = (const long double *) a;
  const long double *fb = (const long double *) b;

  if (!isunordered(*fa, *fb)) {
    return (*fa > *fb) - (*fa < *fb);
  }

  if (!isnan(*fa)) {
    return -1;
  }
  return isnan(*fb);  // return 0 or 1
}

注意:在阅读了许多好的评论并学到了很多东西之后,除了接受另一个答案外,我还发布了 Can I answer my own question? 中提供的自我回答。