Compartilhar via


Método System.Single.Equals

Este artigo fornece comentários complementares à documentação de referência para esta API.

O Single.Equals(Single) método implementa a System.IEquatable<T> interface e executa um pouco melhor do que Single.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, pode ser possível codificar um Equals método em que o tipo de parâmetro tenha 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 bits quanto a instância.

Por exemplo, suponha que o tipo de instância seja Single 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 Single objeto e, em seguida, gera um Single.Equals(Single) 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 implícitas de ampliação de tipos numéricos. Para obter mais informações, consulte Tabelas de Conversão de Tipos.

Precisão em comparações

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

// Initialize two floats with apparently identical values
float float1 = .33333f;
float float2 = 1/3;
// Compare them for equality
Console.WriteLine(float1.Equals(float2));    // displays false
// Initialize two floats with apparently identical values
let float1 = 0.33333f
let float2 = 1f / 3f
// Compare them for equality
printfn $"{float1.Equals float2}"    // displays false
' Initialize two singles with apparently identical values
Dim single1 As Single = .33333
Dim single2 As Single = 1/3
' Compare them for equality
Console.WriteLine(single1.Equals(single2))    ' displays False

Uma técnica de comparação que evita os problemas associados à comparação de igualdade envolve a definição de uma margem aceitável de diferença entre dois valores (como .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á um resultado de diferenças de precisão e, portanto, os valores provavelmente serão iguais. O exemplo a seguir usa essa técnica para comparar .33333 e 1/3, que são os dois Single valores que o exemplo de código anterior considerou desiguais.

// Initialize two floats with apparently identical values
float float1 = .33333f;
float float2 = (float) 1/3;
// Define the tolerance for variation in their values
float difference = Math.Abs(float1 * .0001f);

// Compare the values
// The output to the console indicates that the two values are equal
if (Math.Abs(float1 - float2) <= difference)
   Console.WriteLine("float1 and float2 are equal.");
else
   Console.WriteLine("float1 and float2 are unequal.");
// Initialize two floats with apparently identical values
let float1 = 0.33333f
let float2 = 1f / 3f
// Define the tolerance for variation in their values
let difference = abs (float1 * 0.0001f)

// Compare the values
// The output to the console indicates that the two values are equal
if abs (float1 - float2) <= difference then
    printfn "float1 and float2 are equal."
else
    printfn "float1 and float2 are unequal."
' Initialize two singles with apparently identical values
Dim single1 As Single = .33333
Dim single2 As Single = 1/3
' Define the tolerance for variation in their values
Dim difference As Single = Math.Abs(single1 * .0001f)

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

Nesse caso, os valores são iguais.

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 deve ser maior que Epsilon. Normalmente, é muitas vezes maior que Epsilon. Por isso, recomendamos que você não use Epsilon ao comparar valores Double para verificar igualdade.

Uma segunda técnica que evita os problemas associados à comparação de igualdade 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 serão iguais. Uma maneira de fazer isso é selecionar arbitrariamente um valor absoluto. No entanto, isso é problemático, pois uma margem aceitável de diferença depende da magnitude dos Single valores. Uma segunda maneira aproveita um recurso de design do formato de ponto flutuante: a diferença entre os componentes de mantissa nas representações inteiras de dois valores de ponto flutuante indica o número de possíveis valores de ponto flutuante que separam os dois valores. Por exemplo, a diferença entre 0,0 e Epsilon, é 1, porque Epsilon é o menor valor representável ao trabalhar com um Single 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(Single) método considerou desiguais. Observe que o exemplo usa os métodos BitConverter.GetBytes e BitConverter.ToInt32 para converter um valor de ponto flutuante de precisão simples em sua representação em inteiro.

using System;

public class Example
{
   public static void Main()
   {
      float value1 = .1f * 10f;
      float value2 = 0f;
      for (int ctr = 0; ctr < 10; ctr++)
         value2 += .1f;
         
      Console.WriteLine($"{value1:R} = {value2:R}: {HasMinimalDifference(value1, value2, 1)}");
   }

   public static bool HasMinimalDifference(float value1, float value2, int units)
   {
      byte[] bytes = BitConverter.GetBytes(value1);
      int iValue1 = BitConverter.ToInt32(bytes, 0);
      
      bytes = BitConverter.GetBytes(value2);
      int iValue2 = BitConverter.ToInt32(bytes, 0);
      
      // If the signs are different, return false except for +0 and -0.
      if ((iValue1 >> 31) != (iValue2 >> 31))
      {
         if (value1 == value2)
            return true;
          
         return false;
      }

      int diff = Math.Abs(iValue1 - iValue2);

      if (diff <= units)
         return true;

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

let hasMinimalDifference (value1: float32) (value2: float32) units =
    let bytes = BitConverter.GetBytes value1
    let iValue1 = BitConverter.ToInt32(bytes, 0)
    let bytes = BitConverter.GetBytes(value2)
    let iValue2 = BitConverter.ToInt32(bytes, 0)
    
    // If the signs are different, return false except for +0 and -0.
    if (iValue1 >>> 31) <> (iValue2 >>> 31) then
        value1 = value2
    else
        let diff = abs (iValue1 - iValue2)
        diff <= units

let value1 = 0.1f * 10f
let value2 =
    List.replicate 10 0.1f
    |> List.sum
    
printfn $"{value1:R} = {value2:R}: {hasMinimalDifference value1 value2 1}"
// The example displays the following output:
//        1 = 1.0000001: True
Module Example
   Public Sub Main()
      Dim value1 As Single = .1 * 10
      Dim value2 As Single = 0
      For ctr As Integer =  0 To 9
         value2 += CSng(.1)
      Next
               
      Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2,
                        HasMinimalDifference(value1, value2, 1))
   End Sub

   Public Function HasMinimalDifference(value1 As Single, value2 As Single, units As Integer) As Boolean
      Dim bytes() As Byte = BitConverter.GetBytes(value1)
      Dim iValue1 As Integer =  BitConverter.ToInt32(bytes, 0)
      
      bytes = BitConverter.GetBytes(value2)
      Dim iValue2 As Integer =  BitConverter.ToInt32(bytes, 0)
      
      ' If the signs are different, Return False except for +0 and -0.
      If ((iValue1 >> 31) <> (iValue2 >> 31)) Then
         If value1 = value2 Then
            Return True
         End If           
         Return False
      End If

      Dim diff As Integer =  Math.Abs(iValue1 - iValue2)

      If diff <= units Then
         Return True
      End If

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

A precisão dos números de ponto flutuante além da precisão documentada é específica da implementação e da versão do .NET. Consequentemente, uma comparação de dois números pode produzir resultados diferentes dependendo da versão do .NET, pois a precisão da representação interna dos números pode mudar.