关于C#:如何将长双打与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,则结果相反。

如果compare函数使用return (a > b) - (a < b);,如果一个或两个ab都是NaN,则代码将返回0。数组不会按需要排序,它会丢失总的排序要求。

当使用诸如int isnan(real-floating x);int isfinite(real-floating x);这样的分类函数时,这种类型的long double方面很重要。我知道isfinite( finite_long_double_more_than_DBL_MAX)可能返回错误。因此,我担心isnan(some_long_double)可能会做一些意想不到的事情。

我试过下面的。很明显,它是按需要分类的。

子问题:下面的compare()是否足够按需要排序?有什么建议的简化吗?如果不是-如何修复?(对于此任务,可以对0.0升和-0.0升这样的值进行任何排序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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("
sorted:"
);
  qsort(x, n, sizeof x[0], compare);
  for (size_t i = 0; i < n; i++) {
    printf("%.3Le,", x[i]);
  }
  puts("");
}

产量

1
2
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,

如果我知道比较函数是正确的,我会在代码评审上发布改进意见。然而,我对代码与那些讨厌的nan一起正常工作还没有足够的信心。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

由于EDOCX1[0]的测试在顶部,不应通过任何NAN,因此可以安全地用您的

1
return (a > b) - (a < b);

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

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

为了完成,我用EDCOX1,9和EDCOX1,10(来自C++ EDCOX1,11)进行测试,同样,排序顺序没有差异。


NAN测试

1
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时,该测试评估为0,否则评估为1。不幸的是,除非实现定义了__STDC_IEC_559__,否则这不一定有效。

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

如果至少有一个操作数是NaN,则使用>=, >, <, <=可能导致"无效"浮点异常。因此,之前对NaN的测试是谨慎的,正如@usr2564301所回答的那样。

C提供宏isgreaterequal(), isgreaterequal(), isless(), islessthna(),用于比较,而不是提高"无效"浮点值。例外。这对于double是一个很好的选择,但是宏使用的是一个真正的浮动,这可能与long double不同。isgreater(long_double_a, long_double_a)可作为double进行评价,但不能提供所需的比较结果。

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

以下使用了上述思想,正如我所读到的,C规范对于除少数情况外的所有情况都是定义良好且功能正确的:当long double具有nan但不是真正的浮动(通常double)没有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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
}

注意:在阅读了许多好的评论并学习了很多之后,我会按照"我能回答我自己的问题吗?"中的规定发布这个自我回答。除了接受另一个答案。