Подтвердить что ты не робот

Почему .NET 4.0 сортирует этот массив по-другому, чем .NET 3.5?

В этом вопросе stackoverflow возник интересный вопрос о сортировке двойных массивов с значениями 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();
}

Когда вы запускаете это под платформой .NET 3.5, массив сортируется следующим образом:

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

Когда вы запускаете его под .NET 4.0, массив сортируется несколько более логично:

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

Я могу понять, почему он будет сортировать странно в .NET 3.5 (потому что NaN не равен, меньше или больше, чем угодно). Я также могу понять, почему он будет сортировать так, как это делается в .NET 4.0. Мой вопрос: почему это изменилось с 3.5 до 4.0? И где находится документация Microsoft для этого изменения?

4b9b3361

Ответ 1

Это исправление ошибки. Отчет о обратной связи с деталями ошибок здесь. Ответ Microsoft на отчет об ошибке:

Обратите внимание, что эта ошибка влияет на следующее:

  • Array.Sort(), где массив содержит Double.NaN
  • Array.Sort(), где массив содержит Single.NaN
  • всех вызывающих абонентов, например, в List.Sort(), где список содержит Double.NaN

Эта ошибка будет исправлена ​​в следующей крупной версии среды выполнения; до тех пор вы можете обойти это, используя специальный IComparer, который выполняет правильную сортировку. Как упоминалось в обходных комментариях, не используйте Comparer.Default, потому что это специально с помощью процедуры сортировки ярлыков, которая неправильно обрабатывает NaN. Вместо этого вы можете предоставить свой собственный сравнитель, который обеспечивает эквивалентное сравнение, но не будет специально обложенным.

Ответ 2

Не совсем ответ, но, возможно, подсказка... Вы можете воспроизвести странное поведение 3.5 в 4.0 с помощью этого кода:

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 и a < b возвращают false, если a или b NaN, поэтому метод CompareDouble возвращает 0, поэтому NaN считается равным всем... Это дает тот же результат как в 3.5:

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

Ответ 3

У меня нет кода для среды выполнения .NET 3.5, чтобы проверить это, но я думаю, что они исправили ошибку в стандартном компараторе для double, чтобы привести его в соответствие с документация.

В соответствии с этим документом Double.Compare рассматривает NaN как равный PositiveInfinity и NegativeInfinity и меньше любого другого значения.

Документация одинакова для .NET 3.5 и .NET 4.0, поэтому я должен подумать, что это было исправление ошибки, чтобы код работал как задокументированный.

EDIT:

После прочтения комментариев в связанном вопросе я должен подумать, что проблема не в Double.Compare, а в том, что использует метод Array.Sort (от которого зависит List.Sort) для сравнения double значения. Так или иначе, я не думаю, что это действительно Double.Compare.

Ответ 4

[Это мой ответ, бесстыдный, разорванный с другого поста. Было бы неплохо, если кто-то изучит это дальше - double.CompareTo и double.CompareTo(double) четко определены, как указано ниже, поэтому я подозреваю, что существует какая-то магия Array.Sort для определенного типа.]

Array.Sort(double[]): как представляется, не используется CompareTo(double[]), и это может быть очень ошибкой - обратите внимание на разницу в Array.Sort(object []) и Array.Sort(double []) ниже. Мне хотелось бы уточнить/исправить следующее:

Во-первых, 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).

Сортирует элементы во всем одномерном массиве, используя реализацию IComparable каждого элемента массива.

Аналогично, Array.Sort(T[]) использует CompareTo(T).

Сортирует элементы во всем массиве, используя реализацию универсального интерфейса IComparable (Of T) каждого элемента массива.

Посмотрим:

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) - ПРИМЕЧАНИЕ. ARRAY IS OBJECT, и поведение "ожидается" в контракте CompareTo.

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

Хм. В самом деле. В заключение:

У меня НЕ ИДЕЯ - но я подозреваю, что есть некоторая "оптимизация", в результате чего CompareTo(double) не вызывается.

Счастливое кодирование.