La forma correcta de comparar un System.Double a '0' (un número, int?)

87

Lo siento, esta puede ser una pregunta fácil y estúpida, pero necesito saberlo para estar seguro.

Tengo esta ifexpresión,

void Foo()
{
    System.Double something = GetSomething();
    if (something == 0) //Comparison of floating point numbers with equality 
                     // operator. Possible loss of precision while rounding value
        {}
}

¿Es esa expresión igual a

void Foo()
{
    System.Double something = GetSomething();
    if (something < 1)
        {}
}

? Porque entonces podría tener un problema al ingresar, ifpor ejemplo, con un valor de 0.9.

radbyx
fuente
3
// Comparison of floating point numbers with equality // operator. ¿Realmente necesitas especificar eso? :)
George Johnston
1
Diablos no. Hay muchísimos valores entre 0 y 1. ¿Por qué no probarlo y verlo por sí mismo?
Igby Largeman
12
Escribí lo mismo que hizo Resharper, para mostrar dónde está mi enfoque.
radbyx
@Charles: Además, hay muchos números que son menores que 0.
Brian
posible duplicado de Comparar valores dobles en C #
Dzyann

Respuestas:

114

Bueno, ¿qué tan cerca necesitas que esté el valor de 0? Si pasa por muchas operaciones de punto flotante que en "precisión infinita" pueden resultar en 0, podría terminar con un resultado "muy cercano" a 0.

Por lo general, en esta situación, desea proporcionar algún tipo de épsilon y verificar que el resultado esté dentro de ese épsilon:

if (Math.Abs(something) < 0.001)

El épsilon que debe usar es específico de la aplicación, depende de lo que esté haciendo.

Por supuesto, si el resultado debe ser exactamente cero, una simple verificación de igualdad está bien.

Jon Skeet
fuente
En realidad, necesito que sea exactamente cero, ¿cómo se vería eso? Busqué Double.Zero, pero conoce la suerte. Hay una constante ¿verdad? Por cierto, gracias, obtengo la parte épsilon ahora :)
radbyx
24
@radbyx: Solo usa == 0. Tienes un literal allí, eso es bastante constante :)
Jon Skeet
35

Si somethingse ha asignado a partir del resultado de una operación distinta a something = 0esa, es mejor que utilice:

if(Math.Abs(something) < Double.Epsilon)
{
//do something
}

Editar : este código es incorrecto. Epsilon es el número más pequeño, pero no del todo cero. Cuando desee comparar un número con otro número, debe pensar en cuál es la tolerancia aceptable. Digamos que no le importa nada más allá de .00001. Ese es el número que usarías. El valor depende del dominio. Sin embargo, la mayoría de las veces nunca es Double.Epsilon.

sonatique
fuente
2
Esto no resuelve el problema de redondeo, por ejemplo, Math.Abs(0.1f - 0.1d) < double.Epsilonesfalse
Thomas Lule
6
Doble Epsilon es demasiado pequeño para tal comparación. Doble Epsilon es el número positivo más pequeño que puede representar el doble.
Evgeni Nabokov
3
Esto no tiene sentido porque es prácticamente lo mismo que comparar con 0. -1
Gaspa79
1
El objetivo es comparar con el concepto de 0 sin usar ==. Al menos tiene sentido matemáticamente. Supongo que tienes un doble a la mano y quieres compararlo con el concepto de cero sin ==. Si su doble es diferente de 0d por cualquier motivo, incluido el redondeo, el relleno de prueba devuelve falso. Esta comparación parece válida para cualquier doble y devolverá verdadera solo si este doble es más pequeño que el número más pequeño que se puede representar, lo que parece una buena definición para probar el concepto de 0, ¿no?
sonatique
3
@MaurGi: te equivocas: double d = Math.Sqrt(10100)*2; double a = Math.Sqrt(40400); if(Math.Abs(a - d) < double.Epsilon) { Console.WriteLine("true"); }
sonatique
26

Tu somethinges un double, y has identificado correctamente que en la línea

if (something == 0)

tenemos una doubleen el lado izquierdo (izq.) y una inten el lado derecho (der.).

Pero ahora parece que cree que lhs se convertirá en an int, y luego el ==signo comparará dos enteros. Eso no es lo que pasa. La conversión de double a int es explícita y no puede ocurrir "automáticamente".

En cambio, sucede lo contrario. El rhs se convierte en double, y luego el ==signo se convierte en una prueba de igualdad entre dos dobles. Esta conversión es implícita (automática).

Se considera mejor (por algunos) escribir

if (something == 0.0)

o

if (something == 0d)

porque entonces es inmediato que estás comparando dos dobles. Sin embargo, eso es solo una cuestión de estilo y legibilidad porque el compilador hará lo mismo en cualquier caso.

También es relevante, en algunos casos, introducir una "tolerancia" como en la respuesta de Jon Skeet, pero esa tolerancia también lo sería double. Por supuesto, podría serlo 1.0si quisiera, pero no tiene que ser un entero [el menos estrictamente positivo].

Jeppe Stig Nielsen
fuente
17

Si simplemente desea suprimir la advertencia, haga esto:

if (something.Equals(0.0))

Por supuesto, esta es solo una solución válida si sabe que la deriva no es un problema. A menudo hago esto para comprobar si estoy a punto de dividir por cero.

Russell Phillips
fuente
4

No creo que sea igual, honestamente. Considere su propio ejemplo: algo = 0,9 o 0,0004. En el primer caso será FALSO, en el segundo caso será VERDADERO. Al tratar con estos tipos, suelo definir para mí el porcentaje de precisión y comparar dentro de esa precisión. Depende de tus necesidades. algo como...

if(((int)(something*100)) == 0) {


//do something
}

Espero que esto ayude.

Tigran
fuente
2
algo tiene que ser exactamente cero.
radbyx
entonces eres afortunado, en este caso :)
Tigran
3

Aquí está el ejemplo que presenta el problema (preparado en LinQPad; si no lo tiene, use en Console.Writelinelugar del Dumpmétodo):

void Main()
{
    double x = 0.000001 / 0.1;
    double y = 0.001 * 0.01; 

    double res = (x-y);
    res.Dump();
    (res == 0).Dump();
}

Tanto x como y son teóricamente iguales e iguales a: 0,00001 pero debido a la falta de "precisión infinita", esos valores son ligeramente diferentes. Desafortunadamente, lo suficientemente leve como para regresar falsecuando se compara con 0 de la manera habitual.

michal-loco
fuente