【问题标题】:Sorting an array of Doubles with NaN in it对其中包含 NaN 的 Doubles 数组进行排序
【发布时间】:2011-07-04 15:57:49
【问题描述】:

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

我在工作中遇到了一个问题,我们在表格中使用 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 是如何使排序后的数组以一种奇怪的方式排序的。

引用弗莱的话; “为什么是那些东西?”

【问题讨论】:

  • 我将您的代码复制/粘贴到我的 Visual Studio 中并运行它,它的排序如下:NaN,NaN,1,2,3,4,5,8,9,10。这是.Net 4。当我切换到.Net 3.5SP1时,它的排序就像你说的那样。
  • @pst:如果预期会这样,为什么要改变它? :-)
  • 有关如何弄乱快速排序的其他想法,请参阅我最近关于该主题的系列文章。第一部分在这里:blogs.msdn.com/b/ericlippert/archive/2011/01/20/…
  • @Agent 我在这里问了这个问题:stackoverflow.com/q/5123886/138757 希望有人知道。
  • @Steven 经过深入研究,我同意这是 .NET35 中的一个错误(至少在文档中):-)

标签: c# sorting .net-3.5 nan


【解决方案1】:

我相信那是因为

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

因此,对它们的比较会失败,从而导致整个排序失败。

【讨论】:

  • 就是这样。如果你想要一个合理的行为,你需要写一个自定义的比较器,明确地使用IsNaN。无论比较是什么,所有与 NaN 的比较都是错误的。即使NaN == NaN 也会返回false,这似乎应该违反规则,但实际上并没有。
  • @John 感谢您的澄清。我知道这在 Java 中是正确的,但我不是 C# 人(即使它是我第二个最受好评的答案标签...O_o)
  • 看到这个问题更新会很有趣。 Phil 在 .NET35 和 .NET4 中的评论和行为变化。
  • 原因是因为QuickSort算法;实现执行不稳定的排序;与元素的比较无关
  • @K Ivanov 即使不稳定,它也应该只有 NaN、NaN ... 1、2、3... 如果 NaN 和 NaN/非 NaN 的排序是明确定义的。稳定性只是意味着如果 x1 == x2 那么 x1, x2 可以排序 x1, x2 或 x2, x1(如果不稳定)。
【解决方案2】:

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

请参阅错误报告 Bug in List<double/single>.Sort() [.NET35] in list which contains double.NaN,然后在 Why does .NET 4.0 sort this array differently than .NET 3.5? 上给 Hans Passant 投赞成票,我从那里撕掉了链接。

历史沉思

[请参阅帖子:Why does .NET 4.0 sort this array differently than .NET 3.5?,希望在此特殊问题上可以找到更多有用的讨论。我也在那里交叉发布了这个回复。]

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

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

无论如何,使用&gt;&lt;== 的答案解释了为什么这些运算符不起作用,但无法解释为什么@987654333 @ 导致意外输出。以下是我的一些发现,尽管它们可能是微不足道的。

首先,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[]) 文档来看——没有使用&gt;&lt;==(根据文档)——只有CompareTo(object)

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

同样,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) -- 注意数组是对象,并且行为是根据CompareTo 合同“预期的”。

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

嗯。真的。总结:

我不知道。

编码愉快。

【讨论】:

  • 唯一一个完全触及这个问题的人。
【解决方案3】:

从概念上讲,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);
    }
}

【讨论】:

  • +1 因为这描述了 .NET35 的行为,但是,.NET35/4 差异的任何原因呢? (见菲尔的评论)。另外,是 &gt;/&lt;/!= 使用还是 IComparable?
【解决方案4】:

实际上,奇怪的排序行为是 .NET 3.5 中的一个错误造成的。该错误已在 .NET 4.0 中得到解决。

解决此问题的唯一方法是使用您自己的自定义比较器,或升级到 .NET 4.0。见Why does .NET 4.0 sort this array differently than .NET 3.5?

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • @K Ivanov 即使不稳定,它也应该只有 NaN、NaN ... 1、2、3... 如果 NaN 和 NaN/非 NaN 的排序是明确定义的。稳定性只是意味着如果 x1 == x2 那么 x1, x2 可以排序 x1, x2 或 x2, x1(如果不稳定)。在这种情况下,有 non-Nan ... NaN ... non-NaN。
    猜你喜欢
    • 2019-11-30
    • 2013-07-07
    • 2015-04-06
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 2011-10-27
    • 1970-01-01
    相关资源
    最近更新 更多