c# - sharp - format="{0:c}"
Assert.AreEqual() con System.Double se vuelve realmente confuso (7)
Debido a que los Dobles, como todos los números de punto flotante, son aproximaciones , no valores absolutos representaciones binarias (base-2), que pueden no ser capaces de representar perfectamente fracciones de base-10 (de la misma manera que base-10 no puede representar 1/3 perfectamente) . Entonces, el hecho de que el segundo pase al valor correcto cuando realiza una comparación de igualdad (y el hecho de que el primero no lo haga) es solo suerte, y no un error en el marco o cualquier otra cosa.
Además, lea esto: Convertir un resultado para flotar en el método que devuelve el resultado de los cambios de flotación
Assert.Equals no cubre este caso porque el principio de menos asombro indica que, dado que todos los demás tipos de valores numéricos incorporados en .NET definen .Equals () para realizar una operación equivalente de ==, Double también lo hace. Dado que, de hecho, los dos números que está generando en su prueba (el literal 0.5d y la suma 5x de .1d) no son ==
iguales (los valores reales en los registros de los procesadores son diferentes) Igual a () devuelve falso.
No es la intención del marco romper las reglas de computación generalmente aceptadas para hacer su vida más cómoda.
Finalmente, ofrecería que NUnit se haya dado cuenta de este problema y de acuerdo con http://www.nunit.org/index.php?p=equalConstraint&r=2.5 ofrece el siguiente método para probar la igualdad de punto flotante dentro de una tolerancia:
Assert.That( 5.0, Is.EqualTo( 5 );
Assert.That( 5.5, Is.EqualTo( 5 ).Within(0.075);
Assert.That( 5.5, Is.EqualTo( 5 ).Within(1.5).Percent;
Descripción
¡Este no es un ejemplo del mundo real! Por favor, no sugieras usar decimal
o alguna otra cosa.
Solo pregunto esto porque realmente quiero saber por qué sucede esto.
Hace poco vi de nuevo el impresionante Tekpub Webcast Mastering C # 4.0 con Jon Skeet .
En el episodio 7 - Decimales y Puntos Flotantes se está volviendo realmente extraño e incluso nuestro Chuck Norris de Programación (también conocido como Jon Skeet) no tiene una respuesta real a mi pregunta. Sólo una podría ser .
Pregunta: ¿Por qué MyTestMethod()
y se MyTestMethod2()
?
Ejemplo 1
[Test]
public void MyTestMethod()
{
double d = 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
Console.WriteLine("d = " + d);
Assert.AreEqual(d, 1.0d);
}
Esto resulta en
d = 1
Esperado: 0.999999999999999998d Pero fue: 1.0d
Ejemplo 2
[Test]
public void MyTestMethod2()
{
double d = 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
Console.WriteLine("d = " + d);
Assert.AreEqual(d, 0.5d);
}
Esto resulta en éxito
d = 0,5
Pero por qué ?
Actualizar
¿Por qué Assert.AreEqual()
cubre eso?
Está bien, no he comprobado lo que hace Assert.AreEqual
... pero sospecho que, de forma predeterminada, no está aplicando ninguna tolerancia. No lo esperaría a mis espaldas. Así que busquemos otra explicación ...
Básicamente, está viendo una coincidencia: la respuesta después de cuatro adiciones es el valor exacto, probablemente porque el bit más bajo se pierde en algún lugar cuando cambia la magnitud. No he visto los patrones de bits involucrados, pero si usa DoubleConverter.ToExactString
(mi propio código) puede ver exactamente cuál es el valor en cualquier momento:
using System;
public class Test
{
public static void Main()
{
double d = 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
}
}
Resultados (en mi caja):
d = 0.1000000000000000055511151231257827021181583404541015625
d = 0.200000000000000011102230246251565404236316680908203125
d = 0.3000000000000000444089209850062616169452667236328125
d = 0.40000000000000002220446049250313080847263336181640625
d = 0.5
Ahora, si comienzas con un número diferente, no funciona de la misma manera:
(Comenzando con d = 10.1)
d = 10.0999999999999996447286321199499070644378662109375
d = 10.199999999999999289457264239899814128875732421875
d = 10.2999999999999989341858963598497211933135986328125
d = 10.39999999999999857891452847979962825775146484375
d = 10.4999999999999982236431605997495353221893310546875
Básicamente, tuviste suerte o mala suerte con tu prueba: los errores se cancelaron automáticamente.
Esta es la característica de la aritmética de punto flotante de la computadora ( http://www.eskimo.com/~scs/cclass/progintro/sx5.html )
Es importante recordar que la precisión de los números de punto flotante suele ser limitada, y esto puede llevar a resultados sorprendentes. El resultado de una división como 1/3 no se puede representar exactamente (es una fracción que se repite infinitamente, 0.333333 ...), por lo que el cálculo (1/3) x 3 tiende a producir un resultado como 0.999999 ... en lugar de 1.0. Además, en la base 2, la fracción 1/10, o 0.1 en el decimal, es también una fracción que se repite infinitamente, y tampoco se puede representar exactamente, por lo que (1/10) x 10 también puede producir 0.999999 .... Para estos razones y otras, los cálculos de punto flotante rara vez son exactos. Cuando trabaje con el punto flotante de la computadora, debe tener cuidado de no comparar dos números para una igualdad exacta, y debe asegurarse de que el "error de redondeo" no se acumule hasta que degrade gravemente los resultados de sus cálculos.
Debe establecer explícitamente la precisión para Assert
Por ejemplo:
double precision = 1e-6;
Assert.AreEqual(d, 1.0, precision);
Es un trabajo para tu muestra. A menudo lo uso de esta manera en mi código, pero la precisión depende de la situación
Esto se debe a que los números de punto flotante pierden precisión. La mejor manera de comparar iguales es restar los números y verificar que el diferente sea menor que un número determinado, como .001 (o con la precisión que necesite). Mire http://msdn.microsoft.com/en-us/library/system.double%28v=VS.95%29.aspx específicamente la sección de valores de punto flotante y pérdida de precisión.
Assert.AreEqual
tiene eso en cuenta.
Pero para hacerlo, debe proporcionar su margen de error: el delta dentro de la diferencia entre los dos valores flotantes se considera igual para su aplicación .
Assert.AreEqual
tiene dos sobrecargas que solo toman dos parámetros: uno genérico (T, T)
y uno no genérico (object, object)
. Estos solo pueden hacer las comparaciones por defecto.
Utilice una de las sobrecargas que toman el double
y que también tiene un parámetro para el delta.
0.1
no se puede representar exactamente en un doble debido a su formato interno.
Usa el decimal si quieres representar los números base 10.
Si desea comparar los dobles, compruebe si están dentro de una cantidad muy pequeña entre sí.
Assert.AreEqual()
cubre eso; tienes que usar la sobrecarga con un tercer argumento delta
:
Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);