Comparando valores duplos em c #

Eu tenho uma variável double chamada x . No código, x recebe um valor de 0.1 e eu o verifico em uma instrução ‘if’ comparando x 0.1

 if (x==0.1) { ---- } 

Infelizmente não entra na declaração if

  1. Devo usar o Double ou o double ?

  2. Qual é a razão por trás disso? Você pode sugerir uma solução para isso?

É um problema padrão devido a como o computador armazena valores de ponto flutuante. Procure aqui por “problema de ponto flutuante” e você encontrará toneladas de informações.

Resumindo – um float / double não pode armazenar 0.1 precisamente. Será sempre um pouco fora.

Você pode tentar usar o tipo decimal que armazena números em notação decimal. Assim, 0.1 será representável com precisão.


Você queria saber o motivo:

Float / double são armazenados como frações binárias, não frações decimais. Ilustrar:

12.34 em notação decimal (o que usamos) significa

  1 * 10 1 + 2 * 10 0 + 3 * 10 -1 + 4 * 10 -2 

O computador armazena números de pontos flutuantes da mesma maneira, exceto que usa a base 2 : 10.01

  1 * 2 1 + 0 * 2 0 + 0 * 2 -1 + 1 * 2 -2 

Agora, você provavelmente sabe que existem alguns números que não podem ser representados totalmente com nossa notação decimal. Por exemplo, 1/3 na notação decimal é 0.3333333… . A mesma coisa acontece na notação binária, exceto que os números que não podem ser representados com precisão são diferentes. Entre eles está o número 1/10 . Em notação binária é 0.000110011001100… .

Como a notação binária não pode armazená-lo com precisão, ela é armazenada de maneira arredondada. Daí o seu problema.

double e Double são os mesmos ( double é um apelido para Double ) e podem ser usados ​​de forma intercambiável.

O problema em comparar um duplo com outro valor é que os duplos são valores aproximados, não valores exatos. Então, quando você define x para 0.1 ele pode, na verdade, ser armazenado como 0.100000001 ou algo parecido.

Em vez de verificar a igualdade, você deve verificar se a diferença é menor que uma diferença mínima definida (tolerância). Algo como:

 if (Math.Abs(x - 0.1) < 0.0000001) { ... } 

Você precisa de uma combinação de Math.Abs em XY e um value para comparar.

Você pode usar a seguinte abordagem do método de extensão

 public static class DoubleExtensions { const double _3 = 0.001; const double _4 = 0.0001; const double _5 = 0.00001; const double _6 = 0.000001; const double _7 = 0.0000001; public static bool Equals3DigitPrecision(this double left, double right) { return Math.Abs(left - right) < _3; } public static bool Equals4DigitPrecision(this double left, double right) { return Math.Abs(left - right) < _4; } ... 

Como você raramente chama methods em double, exceto ToString , acredito que seja uma extensão bastante segura.

Então você pode comparar y como

if(x.Equals4DigitPrecision(y))

Comparar o número de ponto flutuante nem sempre pode ser feito precisamente devido ao arredondamento. Comparar

 (x == .1) 

o computador realmente compara

 (x - .1) vs 0 

O resultado da sybtraction nem sempre pode ser representado precisamente por causa de como o número de ponto flutuante é representado na máquina. Portanto, você obtém algum valor diferente de zero e a condição é avaliada como false .

Para superar isso compare

 Math.Abs(x- .1) vs some very small threshold ( like 1E-9) 

Da documentação :

Precisão nas comparações O método Equals deve ser usado com caucanvas, porque dois valores aparentemente equivalentes podem ser desiguais devido à diferença de precisão dos dois valores. O exemplo a seguir relata que o valor Double .3333 e o Double retornado dividindo 1 por 3 são desiguais.

Em vez de comparar a igualdade, uma técnica recomendada envolve definir uma margem de diferença aceitável entre dois valores (como 0,01% de um dos valores). Se o valor absoluto da diferença entre os dois valores for menor ou igual a essa margem, a diferença provavelmente será devida a diferenças na precisão e, portanto, os valores provavelmente serão iguais. O exemplo a seguir usa essa técnica para comparar .33333 e 1/3, os dois valores Double que o exemplo de código anterior encontrou como desiguais.

Então, se você realmente precisa de um duplo, você deve usar a técnica descrita na documentação. Se você puder, mude para um decimal. Será mais lento , mas você não terá esse tipo de problema.

Duplo e duplo são idênticos.

Por esse motivo, consulte http://www.yoda.arachsys.com/csharp/floatingpoint.html . Resumindo: um double não é um tipo exato e uma diferença de um minuto entre “x” e “0.1” irá eliminá-lo.

A comparação exata de valores de ponto flutuante nem sempre funciona devido ao problema de arredondamento e representação interna.

Tente comparação imprecisa:

 if (x >= 0.099 && x < = 0.101) { } 

A outra alternativa é usar o tipo de dados decimal.

Use decimal . Não tem esse “problema”.

1) Devo usar Double ou Double ???

Double e double é a mesma coisa. double é apenas uma palavra-chave do C # funcionando como alias para a class System.Double O mais comum é usar os aliases! O mesmo para string ( System.String ), int ( System.Int32 )

Consulte também Tabela de Tipos Internos (Referência C #)

Duplo (chamado float em algumas linguagens) é repleto de problemas devido a problemas de arredondamento, só é bom se você precisar de valores aproximados.

O tipo de dados Decimal faz o que você deseja.

Para decimal de referência e Decimal são os mesmos no .NET C #, assim como os tipos double e Double, ambos se referem ao mesmo tipo (decimal e double são muito diferentes, como você viu).

Tenha em atenção que o tipo de dados Decimal tem alguns custos associados, por isso, use-o com cuidado se estiver a ver loops, etc.

as representações de números de ponto flutuante são notoriamente imprecisas (devido à forma como os floats são armazenados internamente), por exemplo, x pode na verdade ser 0.0999999999 ou 0.100000001 e sua condição falhará. Se você quiser determinar se os floats são iguais, você precisa especificar se eles são iguais a uma determinada tolerância.

ou seja

 if(x - 0.1 < tol) 

Como uma regra geral:

A representação dupla é boa o suficiente na maioria dos casos, mas pode falhar miseravelmente em algumas situações. Use valores decimais se você precisar de precisão completa (como em aplicativos financeiros).

A maioria dos problemas com duplas não vem da comparação direta, ela costuma ser um resultado do acúmulo de várias operações matemáticas que perturbam exponencialmente o valor devido a erros de arredondamento e frações (especialmente com multiplicações e divisões).

Verifique sua lógica, se o código é:

 x = 0.1 if (x == 0.1) 

não deve falhar, é simples falhar, se o valor X é calculado por meios ou operações mais complexas é bem possível que o método ToString usado pelo depurador esteja usando um arredondamento inteligente, talvez você possa fazer o mesmo (se for muito arriscado de volta para usar o decimal):

 if (x.ToString() == "0.1") 

Um hack legal que encontrei é usar o método .GetHashCode() que retorna um int que representa o dobro, ou seja,

(0.4d + 0.3d + 0.2d + 0.1d).GetHashCode() //returns -1072693248

1d.GetHashCode() //returns 1072693248

Então, como você observou até agora podemos usar algo parecido com isto

 public static bool AccurateEquality(double first,double second) { return Math.Abs(first.GetHashCode()) == Math.Abs(second.GetHashCode()); } 

uso: AccurateEquality((0.4d + 0.3d + 0.2d + 0.1d),1) //returns true

enquanto: (0.4d + 0.3d + 0.2d + 0.1d) == 1d //returns false

Eu tentei em vários casos e parece funcionar bem.

Tomando uma dica da base de código Java, tente usar .CompareTo e teste a comparação zero. Isso pressupõe que a function .CompareTo leva em consideração a igualdade de ponto flutuante de maneira precisa. Por exemplo,

 System.Math.PI.CompareTo(System.Math.PI) == 0 

Esse predicado deve retornar true .

A maioria das formas acima ou seguindo método de extensão estúpido!

 public static bool EqualsTo(this double value, double value2) { var bytes1 = BitConverter.GetBytes(value); var bytes2 = BitConverter.GetBytes(value2); var long1 = BitConverter.ToInt64(bytes1, 0); var long2 = BitConverter.ToInt64(bytes2, 0); return long1 == long2; }