Почему .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 для этого изменения?
Ответы
Ответ 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)
не вызывается.
Счастливое кодирование.