用NaN对双打数组进行排序

这是一个“你能解释这个”类型的问题,而不是其他任何问题。

我在工作中遇到了一个问题,我们在表中使用NaN值,但是当表被排序时,它以一种非常奇怪,奇怪的方式出现。 我认为NaN正在捣乱,所以我写了一个测试应用程序来判断这是否属实。 这就是我所做的。

static void Main(string[] args) { double[] someArray = { 4.0, 2.0, double.NaN, 1.0, 5.0, 3.0, double.NaN, 10.0, 9.0, 8.0 }; foreach (double db in someArray) { Console.WriteLine(db); } Array.Sort(someArray); Console.WriteLine("\n\n"); foreach (double db in someArray) { Console.WriteLine(db); } Console.ReadLine(); } 

结果如下:

之前:

 4,2,NaN,1,5,3,NaN,10,9,8 

后:

 1,4,NaN,2,3,5,8,9,10,NaN 

所以,是的,NaN有些如何使排序的数组以奇怪的方式排序。

引用弗莱; “为什么这些东西?”

编辑(结论。最终结束。):这一个错误。

请参阅列表中包含double.NaN的列表 .Sort()[.NET35]中的错误报告错误,然后在为什么.NET 4.0对此数组的排序与.NET不同的情况下给Hans Passant进行投票。 3.5? 从中我撕开了链接。

历史沉思

[参见文章: 为什么.NET 4.0对此数组的排序与.NET 3.5不同? 希望在这里可以找到关于这一特定问题的更有用的讨论。 我也在那里交叉发布了这个回复。]

在.NET4中由Phil指出的行为是在CompareTo中定义的。 请参阅.NET的double.CompareTo。 这与.NET35中的行为相同,但根据方法文档,两个版本都应该保持一致…

Array.Sort(double[])似乎没有按预期使用CompareTo(double[]) ,这可能是一个错误 – 注意Array.Sort(object [])和Array.Sort的区别(双[])下面 。 我希望对以下内容进行澄清/更正。

在任何情况下,使用><==的答案解释了为什么这些运算符不起作用无法解释为什么Array.Sort会导致意外输出。 以下是我的一些调查结果,尽管它们可能很少。

首先, double.CompareTo(T)方法文档 - 根据文档很好地定义了这个顺序

小于零 :此实例小于值。 - 或者 - 此实例不是数字(NaN),值是数字。

:此实例等于值。 - 或 - 此实例和值都不是数字(NaN),PositiveInfinity或NegativeInfinity。

大于零 :此实例大于值。 - 或 - 此实例是一个数字,值不是数字(NaN)。

在LINQPad(3.5和4,两者都有相同的结果):

 0d.CompareTo(0d).Dump(); // 0 double.NaN.CompareTo(0d).Dump(); // -1 double.NaN.CompareTo(double.NaN).Dump(); // 0 0d.CompareTo(double.NaN).Dump(); // 1 

使用CompareTo(object)具有相同的结果:

 0d.CompareTo((object)0d).Dump(); // 0 double.NaN.CompareTo((object)0d).Dump(); // -1 double.NaN.CompareTo((object)double.NaN).Dump(); // 0 0d.CompareTo((object)double.NaN).Dump(); // 1 

所以这不是问题。

现在,从Array.Sort(object[])文档 - 没有使用><== (根据文档) - 只是CompareTo(object)

使用Array的每个元素的IComparable实现对整个一维Array中的元素进行排序。

同样, Array.Sort(T[])使用CompareTo(T)

使用Array的每个元素的IComparable(Of T)通用接口实现对整个Array中的元素进行排序。

让我们来看看:

LINQPad(4):

 var ar = new double[] {double.NaN, 0, 1, double.NaN}; Array.Sort(ar); ar.Dump(); // NaN, NaN, 0, 1 

LINQPad(3.5):

 var ar = new double[] {double.NaN, 0, 1, double.NaN}; Array.Sort(ar); ar.Dump(); // NaN, 0, NaN, 1 

LINQPad(3.5) - 注意arrays是对象,并且根据CompareTo合同,行为是“预期的”。

 var ar = new object[] {double.NaN, 0d, 1d, double.NaN}; Array.Sort(ar); ar.Dump(); // NaN, NaN, 0, 1 

嗯。 真。 结论:

我不知道。

快乐的编码。

我相信那是因为

 a < NaN == false a > NaN == false a == NaN == false 

所以他们的比较分解了,这就抛出了整个排序。

从概念上讲,NaN不是数字,因此与数字相比毫无意义,因此:

 a < NaN = false for all a, a > NaN = false for all a and NaN != NaN (!) 

要解决这个问题,您需要编写自己的比较器,使用IsNaN使NaN小于(或大于)所有数字,以便它们全部出现在排序的一端。

编辑:以下是比较版本的示例:

 class Program { private static int NaNComparison(double first, double second) { if (double.IsNaN(first)) { if (double.IsNaN(second)) // Throws an argument exception if we don't handle both being NaN return 0; else return -1; } if (double.IsNaN(second)) return 1; if (first == second) return 0; return first < second ? -1 : 1; } static void Main(string[] args) { var doubles = new[] { double.NaN, 2.0, 3.0, 1.0, double.NaN }; Array.Sort(doubles, NaNComparison); } } 

实际上,奇怪的排序行为是.NET 3.5中的一个错误的结果。 这个bug是用.NET 4.0解决的。

解决它的唯一方法是使用您自己的自定义比较器,或升级到.NET 4.0。 请参阅为什么.NET 4.0对此数组的排序与.NET 3.5不同?

因为您使用的是默认排序,即QuickSort算法; 实现执行不稳定的排序; 也就是说,如果两个元素相等,则可能不会保留它们的顺序