c# - ¿Por qué.NET 4.0 ordena esta matriz de forma diferente que.NET 3.5?
(4)
Esta pregunta de stackoverflow planteó una pregunta interesante sobre la ordenación de matrices dobles con valores de NaN. El OP publicó el siguiente código:
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();
}
Cuando ejecuta esto en el marco .NET 3.5, la matriz se ordena de la siguiente manera:
1,4,NaN,2,3,5,8,9,10,NaN
Cuando lo ejecuta en .NET 4.0, la matriz se ordena un poco más lógicamente:
NaN,NaN,1,2,3,4,5,8,9,10
Puedo entender por qué se ordenaría de forma extraña en .NET 3.5 (porque NaN no es igual, menor que, o mayor que, nada). También puedo entender por qué se ordenaría de la forma en que lo hace en .NET 4.0. Mi pregunta es, ¿por qué esto cambió de 3.5 a 4.0? ¿Y dónde está la documentación de Microsoft para este cambio?
Es una corrección de errores. El informe de comentarios con los detalles del error está aquí . La respuesta de Microsoft al informe de error:
Tenga en cuenta que esta falla afecta lo siguiente:
- Array.Sort (), donde la matriz contiene Double.NaN
- Array.Sort (), donde la matriz contiene Single.NaN
- los llamadores de arriba, por ejemplo en List.Sort (), donde lista contiene Double.NaN
Este error se solucionará en la próxima versión principal del tiempo de ejecución; hasta entonces puede solucionar esto utilizando un IComparer personalizado que realice la clasificación correcta. Como se menciona en los comentarios de la solución alternativa, no use Comparer.Default, ya que se trata de una función especial con una rutina de ordenación de acceso directo que no maneja NaN correctamente. En cambio, puede proporcionar su propio comparador que proporcione una comparación equivalente, pero no será especial.
No es realmente una respuesta, pero tal vez una pista ... Puedes reproducir el comportamiento extraño 3.5 en 4.0 con este código:
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;
}
Aquí, a > b
y a < b
devuelven falso si a o b es NaN
, entonces el método CompareDouble
devuelve 0, por lo que NaN
se considera igual a todo ... Esto da el mismo resultado que en 3.5:
1,4,NaN,2,3,5,8,9,10,NaN
No tengo el código para el tiempo de ejecución de .NET 3.5 para verificar esto, pero estoy pensando que corrigieron un error en el comparador predeterminado por double
para ponerlo en línea con la documentación .
De acuerdo con ese documento, Double.Compare
trata a NaN
como igual a PositiveInfinity
y NegativeInfinity
, y menor que cualquier otro valor.
La documentación es la misma para .NET 3.5 y .NET 4.0, así que tengo que pensar que esto fue una corrección de errores para que el código funcione como está documentado.
EDITAR:
Después de leer los comentarios en la pregunta vinculada, tengo que pensar que el problema no estaba en Double.Compare
, sino en cualquier método que Array.Sort
(que es de lo que depende List.Sort
) para comparar los valores Double
. De alguna manera, no creo que sea realmente Double.Compare
.
[Esta es mi respuesta desvergonzada arrancada de la otra publicación. Sería bueno si alguien explora esto más a fondo: double.CompareTo
y double.CompareTo(double)
están bien definidos, como se indica a continuación, así que sospecho que hay algo de magia Array.Sort
sucediendo para el tipo específico.]
Array.Sort(double[])
: no parece estar usando CompareTo(double[])
como se esperaba y esto bien podría ser un error: observe la diferencia en Array.Sort (object []) y Array.Sort (doble []) a continuación . Me encantaría aclaraciones / correcciones sobre lo siguiente.
Primero, la documentación del método double.CompareTo(T)
: este orden está bien definido según la documentación :
Menos que cero : esta instancia es menor que valor. -o-- Esta instancia no es un número (NaN) y el valor es un número.
Cero : esta instancia es igual a valor. -o-- Tanto esta instancia como el valor no son un número (NaN), PositiveInfinity o NegativeInfinity.
Mayor que cero : esta instancia es mayor que el valor. -o-- Esta instancia es un número y el valor no es un número (NaN).
En LINQPad (3.5 y 4, ambos tienen los mismos resultados):
0d.CompareTo(0d).Dump(); // 0
double.NaN.CompareTo(0d).Dump(); // -1
double.NaN.CompareTo(double.NaN).Dump(); // 0
0d.CompareTo(double.NaN).Dump(); // 1
El uso de CompareTo(object)
tiene los mismos resultados:
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
Entonces ese no es el problema.
Ahora, desde la documentación Array.Sort(object[])
- no se usa >
, <
o ==
(según la documentación) - simplemente CompareTo(object)
.
Ordena los elementos en una matriz unidimensional completa utilizando la implementación
IComparable
de cada elemento de la matriz.
Del mismo modo, Array.Sort(T[])
usa CompareTo(T)
.
Ordena los elementos en una matriz completa utilizando la implementación de la interfaz genérica IComparable (Of T) de cada elemento de la matriz.
Veamos:
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) - NOTE QUE EL ARRAY ES DE OBJETO y el comportamiento es "esperado" según el contrato CompareTo
.
var ar = new object[] {double.NaN, 0d, 1d, double.NaN};
Array.Sort(ar);
ar.Dump();
// NaN, NaN, 0, 1
Hmm. De Verdad. En conclusión:
NO TENGO IDEA , pero sospecho que hay alguna "optimización" que provoca que CompareTo(double)
no se invoque.
Feliz codificación.