Compartilhar via


Método System.Double.Equals

O método Th Double.Equals(Double) implementa a System.IEquatable<T> interface e executa um pouco melhor do que Double.Equals(Object) porque não precisa converter o obj parâmetro em um objeto.

Conversões de expansão

Dependendo da linguagem de programação, talvez seja possível codificar um Equals método em que o tipo de parâmetro tem menos bits (é mais estreito) do que o tipo de instância. Isso é possível porque algumas linguagens de programação executam uma conversão de ampliação implícita que representa o parâmetro como um tipo com tantos bit quanto a instância.

Por exemplo, suponha que o tipo de instância seja Double e o tipo de parâmetro seja Int32. O compilador do Microsoft C# gera instruções para representar o valor do parâmetro como um Double objeto e, em seguida, gera um Double.Equals(Double) método que compara os valores da instância e a representação ampliada do parâmetro.

Consulte a documentação da linguagem de programação para determinar se o compilador executa conversões ampliadoras implícitas de tipos numéricos. Para obter mais informações, consulte o tópico Tabelas de conversão de tipo.

Precisão nas comparações

O Equals método deve ser usado com cautela, pois dois valores aparentemente equivalentes podem ser desiguais devido à precisão diferente dos dois valores. O exemplo a seguir relata que o Double valor .333333 e o Double valor retornado dividindo 1 por 3 são desiguais.

// Initialize two doubles with apparently identical values
double double1 = .33333;
double double2 = (double) 1/3;
// Compare them for equality
Console.WriteLine(double1.Equals(double2));    // displays false
// Initialize two doubles with apparently identical values
let double1 = 0.33333
let double2 = double (1 / 3)
// Compare them for equality
printfn $"{double1.Equals double2}"    // displays false
' Initialize two doubles with apparently identical values
Dim double1 As Double = .33333
Dim double2 As Double = 1/3
' Compare them for equality
Console.WriteLine(double1.Equals(double2))    ' displays False

Em vez de comparar para igualdade, uma técnica envolve definir uma margem relativa aceitável de diferença entre dois valores (como .001% de um dos valores). Se o valor absoluto da diferença entre os dois valores for inferior ou igual a essa margem, é provável que a diferença se deva a diferenças de precisão e, por conseguinte, é provável que os valores sejam iguais. O exemplo a seguir usa essa técnica para comparar .33333 e 1/3, os dois Double valores que o exemplo de código anterior considerou desiguais. Nesse caso, os valores são iguais.

// Initialize two doubles with apparently identical values
double double1 = .333333;
double double2 = (double) 1/3;
// Define the tolerance for variation in their values
double difference = Math.Abs(double1 * .00001);

// Compare the values
// The output to the console indicates that the two values are equal
if (Math.Abs(double1 - double2) <= difference)
   Console.WriteLine("double1 and double2 are equal.");
else
   Console.WriteLine("double1 and double2 are unequal.");
// Initialize two doubles with apparently identical values
let double1 = 0.333333
let double2 = double (1 / 3)
// Define the tolerance for variation in their values
let difference = abs (double1 * 0.00001)

// Compare the values
// The output to the console indicates that the two values are equal
if abs (double1 - double2) <= difference then
    printfn "double1 and double2 are equal."
else
    printfn "double1 and double2 are unequal."
' Initialize two doubles with apparently identical values
Dim double1 As Double = .33333
Dim double2 As Double = 1/3
' Define the tolerance for variation in their values
Dim difference As Double = Math.Abs(double1 * .00001)

' Compare the values
' The output to the console indicates that the two values are equal
If Math.Abs(double1 - double2) <= difference Then
   Console.WriteLine("double1 and double2 are equal.")
Else
   Console.WriteLine("double1 and double2 are unequal.")
End If

Observação

Como Epsilon define a expressão mínima de um valor positivo cujo intervalo é próximo de zero, a margem de diferença entre dois valores semelhantes deve ser maior que Epsilon. Normalmente, é muitas vezes maior do que Epsilon. Por isso, recomendamos que você não use Epsilon ao comparar Double valores para igualdade.

Uma segunda técnica envolve comparar a diferença entre dois números de ponto flutuante com algum valor absoluto. Se a diferença for menor ou igual a esse valor absoluto, os números serão iguais. Se for maior, os números não são iguais. Uma alternativa é selecionar arbitrariamente um valor absoluto. Isso é problemático, no entanto, porque uma margem de diferença aceitável depende da magnitude dos Double valores. Uma segunda alternativa aproveita um recurso de design do formato de ponto flutuante: a diferença entre a representação inteira de dois valores de ponto flutuante indica o número de possíveis valores de ponto flutuante que os separa. Por exemplo, a diferença entre 0,0 e Epsilon é 1, porque Epsilon é o menor valor representável quando se trabalha com um Double cujo valor é zero. O exemplo a seguir usa essa técnica para comparar .33333 e 1/3, que são os dois Double valores que o exemplo de código anterior com o Equals(Double) método encontrado para ser desigual. O exemplo usa o BitConverter.DoubleToInt64Bits método para converter um valor de ponto flutuante de precisão dupla em sua representação inteira. O exemplo declara os valores como iguais se não houver valores de ponto flutuante possíveis entre suas representações inteiras.

public static void Main()
{
    // Initialize the values.
    double value1 = .1 * 10;
    double value2 = 0;
    for (int ctr = 0; ctr < 10; ctr++)
        value2 += .1;

    Console.WriteLine($"{value1:R} = {value2:R}: " +
        $"{HasMinimalDifference(value1, value2, 1)}");
}

public static bool HasMinimalDifference(
    double value1,
    double value2,
    int allowableDifference
    )
{
    // Convert the double values to long values.
    long lValue1 = BitConverter.DoubleToInt64Bits(value1);
    long lValue2 = BitConverter.DoubleToInt64Bits(value2);

    // If the signs are different, return false except for +0 and -0.
    if ((lValue1 >> 63) != (lValue2 >> 63))
    {
        if (value1 == value2)
            return true;

        return false;
    }

    // Calculate the number of possible
    // floating-point values in the difference.
    long diff = Math.Abs(lValue1 - lValue2);

    if (diff <= allowableDifference)
        return true;

    return false;
}
// The example displays the following output:
//
//        1 = 0.99999999999999989: True
open System

let hasMinimalDifference (value1: double) (value2: double) (units: int) =
    let lValue1 = BitConverter.DoubleToInt64Bits value1
    let lValue2 = BitConverter.DoubleToInt64Bits value2

    // If the signs are different, return false except for +0 and -0.
    if (lValue1 >>> 63) <> (lValue2 >>> 63) then
        value1 = value2
    else
        let diff = abs (lValue1 - lValue2)

        diff <= int64 units

let value1 = 0.1 * 10.
let mutable value2 = 0.
for _ = 0 to 9 do
    value2 <- value2 + 0.1

printfn $"{value1:R} = {value2:R}: {hasMinimalDifference value1 value2 1}"
                

// The example displays the following output:
//        1 = 0.99999999999999989: True
Module Example
   Public Sub Main()
      Dim value1 As Double = .1 * 10
      Dim value2 As Double = 0
      For ctr As Integer =  0 To 9
         value2 += .1
      Next
               
      Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2,
                        HasMinimalDifference(value1, value2, 1))
   End Sub

   Public Function HasMinimalDifference(value1 As Double, value2 As Double, units As Integer) As Boolean
      Dim lValue1 As long =  BitConverter.DoubleToInt64Bits(value1)
      Dim lValue2 As long =  BitConverter.DoubleToInt64Bits(value2)
      
      ' If the signs are different, Return False except for +0 and -0.
      If ((lValue1 >> 63) <> (lValue2 >> 63)) Then
         If value1 = value2 Then
            Return True
         End If           
         Return False
      End If

      Dim diff As Long =  Math.Abs(lValue1 - lValue2)

      If diff <= units Then
         Return True
      End If

      Return False
   End Function
End Module
' The example displays the following output:
'       1 = 0.99999999999999989: True

Observação

Para alguns valores, você pode considerá-los iguais mesmo quando houver um possível valor de ponto flutuante entre as representações inteiras. Por exemplo, considere os valores duplos 0.39 e 1.69 - 1.3 (que é calculado como 0.3899999999999999). Em um computador little-endian, as representações inteiras desses valores são 4600697235336603894 e 4600697235336603892, respectivamente. A diferença entre os valores inteiros é 2, o que significa que há um possível valor de ponto flutuante entre 0.39 e 1.69 - 1.3.

Diferenças de versão

A precisão dos números de ponto flutuante além da precisão documentada é específica para a implementação e a versão do .NET. Consequentemente, uma comparação de dois números específicos pode mudar entre as versões do .NET porque a precisão da representação interna dos números pode mudar.

NaN

Se dois Double.NaN valores forem testados para igualdade chamando o Equals método, o método retornará true. No entanto, se dois Double.NaN valores forem testados para igualdade usando o operador equality, o operador retornará false. Quando você deseja determinar se o valor de a Double não é um número (NaN), uma alternativa é chamar o IsNaN método.