A maneira certa de comparar um System.Double a '0' (um número, int?)

87

Desculpe, essa pode ser uma pergunta fácil e estúpida, mas preciso saber para ter certeza.

Eu tenho essa ifexpressão,

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

Essa expressão é igual a

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

? Porque então eu posso ter um problema, inserindo o ifcom, por exemplo, um valor de 0,9.

radbyx
fonte
3
// Comparison of floating point numbers with equality // operator. Você realmente precisava especificar isso? :)
George Johnston
1
Heck, não. Existem muitos valores entre 0 e 1. Por que não apenas testar e ver por si mesmo?
Igby Largeman
12
Acabei de escrever o mesmo que Resharper fez, para mostrar onde está meu foco.
radbyx
@Charles: Além disso, há muitos números menores que 0.
Brian
possível duplicata de Comparação de valores duplos em C #
Dzyann

Respostas:

115

Bem, quão próximo você precisa que o valor esteja de 0? Se você passar por muitas operações de ponto flutuante que em "precisão infinita" podem resultar em 0, você pode acabar com um resultado "muito próximo" de 0.

Normalmente, nesta situação, você deseja fornecer algum tipo de épsilon e verificar se o resultado está exatamente dentro desse épsilon:

if (Math.Abs(something) < 0.001)

O epsilon que você deve usar é específico do aplicativo - depende do que você está fazendo.

Obviamente, se o resultado for exatamente zero, uma simples verificação de igualdade é adequada.

Jon Skeet
fonte
Na verdade, eu preciso que seja exatamente zero, como ficaria? Procurei Double.Zero, mas saiba sorte. Existe uma constante, certo? A propósito, obrigado, recebo a parte do epsilon agora :)
radbyx
24
@radbyx: Basta usar == 0. Você tem um literal aí - que é bastante constante :)
Jon Skeet
35

Se somethingfoi atribuído a partir do resultado de uma operação diferente de something = 0então, é melhor usar:

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

Edit : Este código está errado. Epsilon é o menor número, mas não exatamente zero. Quando você deseja comparar um número a outro, precisa pensar em qual é a tolerância aceitável. Digamos que qualquer coisa além de 0,00001 você não se importe. Esse é o número que você usaria. O valor depende do domínio. No entanto, certamente nunca é Double.Epsilon.

sonatique
fonte
2
Isso não resolve o problema de arredondamento, por exemplo Math.Abs(0.1f - 0.1d) < double.Epsilonéfalse
Thomas Lule
6
Double.Epsilon é muito pequeno para tal comparação. Double.Epsilon é o menor número positivo que o double pode representar.
Evgeni Nabokov
3
Isso não faz sentido porque é praticamente o mesmo que comparar contra 0. -1
Gaspa79
1
O objetivo é comparar com o conceito de 0 sem usar ==. Pelo menos faz sentido matematicamente. Estou assumindo que você tem um duplo em mãos e deseja compará-lo ao conceito de zero sem ==. Se o seu duplo for diferente de 0d por qualquer motivo, incluindo arredondamento, o preenchimento do teste retorna falso. Essa comparação parece válida para qualquer duplo e só retornará verdadeira se esse duplo for menor que o menor número que pode ser representado, o que parece uma boa definição para testar o conceito de 0, não?
sonatique
3
@MaurGi: você está errado: double d = Math.Sqrt(10100)*2; double a = Math.Sqrt(40400); if(Math.Abs(a - d) < double.Epsilon) { Console.WriteLine("true"); }
sonatique
26

Seu somethingé um double, e você o identificou corretamente na linha

if (something == 0)

temos um doubleno lado esquerdo (lhs) e um intno lado direito (rhs).

Mas agora parece que você acha que lhs será convertido em um int, e então o ==sinal irá comparar dois inteiros. Não é isso que acontece. A conversão de double para int é explícita e não pode acontecer "automaticamente".

Em vez disso, acontece o oposto. O rhs é convertido em double, e então o ==sinal se torna um teste de igualdade entre duas duplas. Essa conversão é implícita (automática).

É considerado melhor (por alguns) escrever

if (something == 0.0)

ou

if (something == 0d)

porque então é imediato que você está comparando duas duplas. No entanto, é apenas uma questão de estilo e legibilidade, porque o compilador fará a mesma coisa em qualquer caso.

Também é relevante, em alguns casos, introduzir uma "tolerância" como na resposta de Jon Skeet, mas essa tolerância também seria double. Ele poderia ser, obviamente, 1.0se você queria, mas ele não tem que ser [o mínimo estritamente positivo] inteiro.

Jeppe Stig Nielsen
fonte
17

Se você simplesmente deseja suprimir o aviso, faça o seguinte:

if (something.Equals(0.0))

Claro, esta é uma solução válida apenas se você souber que a deriva não é uma preocupação. Costumo fazer isso para verificar se estou prestes a dividir por zero.

Russell Phillips
fonte
4

Eu não acho que seja igual, honestamente. Considere seu próprio exemplo: algo = 0,9 ou 0,0004. No primeiro caso será FALSE, no segundo caso será TRUE. Ao lidar com esses tipos, normalmente defino para mim a porcentagem de precisão e comparo dentro dessa precisão. Depende de suas necessidades. algo como...

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


//do something
}

Espero que isto ajude.

Tigran
fonte
2
algo tem que ser exatamente zero.
radbyx
então você é um cara de sorte, neste caso :)
Tigran
3

Aqui está o exemplo que apresenta o problema (preparado no LinQPad - se você não tiver, use em Console.Writelinevez do 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 são teoricamente iguais e iguais a: 0,00001, mas devido à falta de "precisão infinita", esses valores são ligeiramente diferentes. Infelizmente, um pouco o suficiente para retornar falseao comparar com 0 da maneira usual.

louco por michal
fonte