【发布时间】:2018-06-12 15:46:08
【问题描述】:
如何比较长双打与
qsort()和关于not-a-number?
在对可能包含非数字的数组进行排序时,我想将所有 NAN 放在排序后的数组的一端。
qsort() 对比较函数施加了一些限制。
函数应返回一个小于、等于或 如果第一个参数分别被认为小于、等于或大于第二个参数,则大于零。
C11dr §7.22.5.2 3当相同的对象...被多次传递给比较函数时,结果应相互一致。也就是说,对于
qsort,他们应该在数组上定义一个total ordering,......同一个对象应该总是以相同的方式与键进行比较。
§7.22.5 4
当a <= b 或a 不是数字或b 不是数字时,a > b 为假。所以a > b 与!(a <= b) 不一样,因为如果其中一个是 NaN,它们会产生相反的结果。
如果比较函数使用return (a > b) - (a < b);,如果a 或b 中的一个或两个为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.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,
如果我知道比较函数是正确的,我会在 Code Review 上发布改进想法。然而,我对代码与那些讨厌的 NaN 正常工作的信心不足。
【问题讨论】:
-
检查 NaN-ness 的数字;如果一个是 NaN 而另一个不是,则报告 NaN 更小(或更大,取决于排序方向和您希望 NaN 出现的位置)。据推测,如果它们都是 NaN,则返回 0。否则,它们都不是 NaN,您可以通过适当的比较来处理它们。如果您认为 NaN 中有不同的值,那么您必须对 NaN 进行表征并根据您选择的规则进行有效比较。请注意,对 NaN 的检查可能应该先进行,而不是最后。
-
@MichaelDorgan:如果您认为两个“假定相等”的数字“不能表示相同”,我认为您要么是错误的,要么是错误的陈述。
-
@EOF 对于此代码,2 个不同的 NaN 可以比较相等 - 它们只会以某种顺序出现在排序列表中。返回 0 不违反 §7.22.5 4.
-
@chux:我会使用
isnan(),但我的要求并没有那么严格,以至于想要识别不同类型的 NaN。我知道信令和非信令 NaN;我相信有许多代表 NaN 的位模式。但我从来不需要足够详细地研究它们来了解isnan()表面下的内容。关键的一项是,如果数组中有两个元素——比如x[10]和x[30]——那么比较x[10]和x[30]应该会产生与比较x[30]和x[10]一致的结果。如果一个是负数,另一个必须是正数,或者两者都为零。 -
为什么不在排序前从数组中删除 NaN?即使您可以在存在 NaN 的情况下进行排序,任何后续代码都需要以某种方式处理它们的存在 - 选项只不过是忽略、丢弃或抱怨。删除 NaN 允许后续代码假设它们不存在,即更少需要检查。