如何比较长双打与qsort和NaN?
如何比较长双打与
qsort()
和关于非数字 ?
在排序可能包含非数字的数组时,我想将所有这些NAN
放在已排序数组的一端。
qsort()
对比较函数施加了一些限制。
如果第一个参数被认为分别小于,等于或大于第二个参数,则该函数应返回小于,等于或大于零的整数。
C11dr§7.22.5.23当相同的对象…不止一次传递给比较函数时,结果应该彼此一致。 也就是说,对于
qsort
它们应该在数组上定义一个总排序 ,…同一个对象应始终以相同的方式与密钥进行比较。
§7.22.54
当a <= b
或a
不是a数或b
不是a数时, a > b
为假。 所以a > b
与!(a <= b)
因为如果其中一个是NaN,它们会产生相反的结果。
如果compare函数使用return (a > b) - (a < b);
如果a
或b
中的一个或两个是NaN,则代码将返回0。 数组不会按照需要排序,它会丢失总排序要求。
当使用像int isnan(real-floating x);
这样的分类函数时,这种long double
方面很重要int isnan(real-floating x);
或int isfinite(real-floating x);
。 我知道isfinite( finite_long_double_more_than_DBL_MAX)
可能会返回false。 所以我担心isnan(some_long_double)
可能会做出意想不到的事情 。
我试过以下。 它显然按要求排序。
子问题:下面的compare()
是否足以按要求排序? 任何建议的简化? 如果没有 – 如何解决? (对于此任务,0.0L和-0.0L之类的值可以以任何方式排序)
#include #include #include #include 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,
如果我知道比较function是正确的,我会发布Code Review以获得改进的想法。 然而,我没有足够的信心,那些代码可以正常地与那些讨厌的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]
都不会产生其他结果。 gcc还没有-ffast-math
, -funsafe-math-optimizations
,甚至-ffinite-math-only
(后者最可能的原因是除了与NaN
的直接比较之外没有其他操作)。
为了完成,我测试了std::numeric_limits
和std::numeric_limits
(来自C ++
) - 同样,排序顺序没有区别。
NaN测试
int isnan(real-floating x);
isnan
宏确定其参数值是否为NaN。 首先,以比其语义类型更宽的格式表示的参数被转换为其语义类型。 然后确定基于参数的类型。 235
235对于isnan
宏,除非实现在评估类型中支持NaN,但在语义类型中不支持NaN,否则确定类型无关紧要。
除了在一个罕见的平台上, 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时,使用>=, >, <, <=
会导致“无效”浮点exception。 因此, @ usr2564301对NaN
先前测试是谨慎的
C提供宏isgreaterequal(), isgreaterequal(), isless(), islessthna()
,它们进行比较而不引发“无效”浮点exception。 这是double
好选择,但宏使用的是real-floating ,它可能与long double
不同。 isgreater(long_double_a, long_double_a)
可以计算为double
并且不提供所需的比较结果。
分类宏的挑战是语义类型可能比long double
窄。
以下使用了上述想法,并且正如我所读到的,C规范在所有情况下都有明确的定义和function正确,除了罕见的情况:当long double
有NaN而不是real-floating (通常是double
)时没有。
#include // 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 }
注意:在阅读了许多好的评论并学到很多东西之后,我发布了这个自我答案, 我可以回答我自己的问题吗? 除了接受另一个答案。