为什么.NET 4.0以与.NET 3.5不同的方式对此数组进行sorting?

这个stackoverflow问题提出了一个有趣的问题关于sorting与NaN值的双数组。 OP发布了以下代码:

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(); } 

在.NET 3.5框架下运行时,数组的sorting如下:

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

当你在.NET 4.0下运行它时,数组在逻辑上被sorting得更多一些:

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

我可以理解为什么它会在.NET 3.5中奇怪地sorting(因为NaN不等于,小于或大于任何东西)。 我也可以理解为什么它会按照.NET 4.0的方式进行sorting。 我的问题是,为什么这个从3.5变成4.0? 微软的这个改变文件在哪里?

这是一个错误修复。 有错误细节的反馈报告在这里 。 微软对错误报告的回应:

请注意,这个错误会影响以下内容:

  • Array.Sort(),其中数组包含Double.NaN
  • Array.Sort(),其中数组包含Single.NaN
  • 上面的任何调用者,例如List.Sort(),其中列表包含Double.NaN

该错误将在下一个主要版本的运行时中得到解决。 直到那时你可以通过使用自定义的IComparer来做到正确的sorting。 正如在变通办法评论中提到的,不要使用Comparer.Default,因为这是特殊的,不适合使用NaN的快捷方式sorting例程。 相反,你可以提供你自己的比较器,提供了一个等效的比较,但不会是特殊的。

不是一个真正的答案,但也许是一个线索…你可以用这个代码在4.0中重现奇怪的3.5行为:

 void Main() { double[] someArray = { 4.0, 2.0, double.NaN, 1.0, 5.0, 3.0, double.NaN, 10.0, 9.0, 8.0 }; Array.Sort(someArray, CompareDouble); someArray.Dump(); } int CompareDouble(double a, double b) { if (a > b) return 1; if (a < b) return -1; return 0; } 

这里,如果a或b是NaN ,那么a > ba < b返回false,所以CompareDouble方法返回0,所以NaN被认为等于一切…这给出了与3.5中相同的结果:

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

我没有用于validation这一点的.NET 3.5运行时代码,但是我认为他们修复了默认比较器中的一个错误,使其与文档一致 。

根据该文件, Double.CompareNaN等同于PositiveInfinityNegativeInfinity ,并且小于任何其他值。

该文档是相同的.NET 3.5和.NET 4.0,所以我不得不认为这是一个错误修复,使代码工作logging。

编辑:

在阅读链接问题中的注释之后,我不得不认为问题不在Double.Compare ,而是在Array.Sort (这是List.Sort依赖的)用来比较Double值的任何方法中。 不知何故,我不认为这真的是Double.Compare

[这是我从另一个post中无耻地撕下的回应。 这将是很好的是有人探讨了这一点 – double.CompareTodouble.CompareTo(double)是定义良好,如下所示,所以我怀疑有一些Array.Sort魔术发生在特定types。

Array.Sort(double[])似乎没有像预期的那样使用CompareTo(double[]) ,这很可能是一个bug – 注意Array.Sort(object [])和Array.Sort (double []) 。 我希望澄清/更正以下内容。

首先, double.CompareTo(T)方法文档 – 这个sorting是根据文档定义的

小于零 :此实例小于值。 – 或者 – 此实例不是一个数字(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中的元素进行sorting。

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

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

让我们来看看:

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) – 注意数组的对象和行为是“预期”每个CompareTo合约。

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

嗯。 真。 结论是:

我没有想法 – 但我怀疑有一些“优化”导致CompareTo(double)不被调用。

快乐的编码。