如何将长双打与 qsort 以及关于 NaN 进行比较?
Posted
技术标签:
【中文标题】如何将长双打与 qsort 以及关于 NaN 进行比较?【英文标题】:How to compare long doubles with qsort and with regard to NaN? 【发布时间】: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 允许后续代码假设它们不存在,即更少需要检查。
【参考方案1】:
这只是对测试的简单重新排序,但如果您愿意,它会使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<double>::signaling_NaN();
和 std::numeric_limits<double>::quiet_NaN();
(来自 C++ <limits.h>
)——同样,排序顺序没有区别。
【讨论】:
这可能会使 NaN 的状态更加清晰,但您认为这与原始compare()
- 有什么功能差异(如果有)?对于 NaN 很少见的典型数据集,首先测试 NaN 显得较慢。那么唯一的优势是代码清晰吗?
@chux:你在质疑你的日常安排是否正确。此代码使用另一种测试顺序,这可能导致不同的结果。因为它没有,它似乎回答了你的问题“[我的]compare()
足够了..”(用“是”?)。因此,没有什么可修复的,并且几乎没有简化的空间。 (我认为重新排序测试因此更具可读性,但没有副作用,我认为没有或只有很小的计算优势。)
首先进行isNaN
测试:这是为了消除关于可能未定义行为的任何挥之不去的疑虑。这样,NaN
将首先被淘汰。一个小提示:这些测试中的每一个都被编译为单个 fucomi
指令。不知道这是否会导致显着放缓(流水线等等——真的,不知道)。
这是好的答案和comment 的力量导致接受这个答案。
@chux:干杯,伙计!我对回答您的高度技术问题有些怀疑,尤其是在关注 cmets 之后(值得保留)。并感谢您对我的回答提出质疑,很高兴!【参考方案2】:
NaN 测试
int isnan(real-floating x);
isnan
宏确定其参数值是否为 NaN。首先,以比其语义类型更宽的格式表示的参数被转换为其语义类型。然后根据参数的类型确定。235235 对于isnan
宏,确定的类型无关紧要,除非实现支持 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 时使用 >=, >, <, <=
会导致“无效”浮点异常。因此NaN
的先前测试是谨慎的,正如@usr2564301 所回答的那样
C 提供宏 isgreaterequal(), isgreaterequal(), isless(), islessthna()
进行比较并且不会引发“无效”浮点
例外。这对于double
来说是一个不错的选择,但宏使用可能与long double
不同的real-floating。 isgreater(long_double_a, long_double_a)
可能评估为 double
并且不提供所需的比较结果。
分类宏的挑战在于语义类型可能比long double
窄。
以下使用上述想法,并且当我阅读 C 规范时,除了罕见的情况外,所有情况下都定义明确且功能正确:当 long double
具有 NaN 但不具有 real-floating (通常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
注意:在阅读了许多优秀的 cmets 并学到了很多东西之后,除了接受另一个答案之外,我还发布了 Can I answer my own question? 中提供的这个自我答案。
【讨论】:
以上是关于如何将长双打与 qsort 以及关于 NaN 进行比较?的主要内容,如果未能解决你的问题,请参考以下文章