Método System.Single.Equals

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

El Single.Equals(Single) método implementa la System.IEquatable<T> interfaz y funciona ligeramente mejor que Single.Equals(Object) porque no tiene que convertir el obj parámetro en un objeto.

Conversiones de ampliación

En función del lenguaje de programación, puede ser posible codificar un Equals método en el que el tipo de parámetro tenga menos bits (es más estrecho) que el tipo de instancia. Esto es posible porque algunos lenguajes de programación realizan una conversión de ampliación implícita que representa el parámetro como un tipo con tantos bits como la instancia.

Por ejemplo, supongamos que el tipo de instancia es Single y el tipo de parámetro es Int32. El compilador de Microsoft C# genera instrucciones para representar el valor del parámetro como un Single objeto y, a continuación, genera un Single.Equals(Single) método que compara los valores de la instancia y la representación ampliada del parámetro.

Consulte la documentación del lenguaje de programación para determinar si su compilador realiza conversiones implícitas de ampliación de tipos numéricos. Para obtener más información, vea Tablas de conversión de tipos.

Precisión en comparaciones

El Equals método debe usarse con precaución, ya que dos valores aparentemente equivalentes pueden ser diferentes debido a la precisión diferente de los dos valores. En el ejemplo siguiente se informa de que el Single valor .3333 y el Single devuelto dividiendo 1 por 3 son distintos.

// 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

Una técnica de comparación que evita los problemas asociados a la comparación de igualdad implica definir un margen aceptable de diferencia entre dos valores (como .01 % de uno de los valores). Si el valor absoluto de la diferencia entre los dos valores es menor o igual que ese margen, es probable que la diferencia sea un resultado de diferencias de precisión y, por lo tanto, es probable que los valores sean iguales. En el ejemplo siguiente se usa esta técnica para comparar .33333 y 1/3, que son los dos Single valores que el ejemplo de código anterior encontró que era desigual.

// 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

En este caso, los valores son iguales.

Nota:

Dado que Epsilon define la expresión mínima de un valor positivo cuyo intervalo está cerca de cero, el margen de diferencia debe ser mayor que Epsilon. Normalmente, es muchas veces mayor que Epsilon. Por este motivo, se recomienda no usar Epsilon al comparar Double los valores de igualdad.

Una segunda técnica que evita los problemas asociados a la comparación de igualdad implica comparar la diferencia entre dos números de punto flotante con algún valor absoluto. Si la diferencia es menor o igual que ese valor absoluto, los números son iguales. Si es mayor, los números no son iguales. Una manera de hacerlo es seleccionar arbitrariamente un valor absoluto. Sin embargo, esto es problemático, ya que un margen aceptable de diferencia depende de la magnitud de los Single valores. Una segunda forma aprovecha una característica de diseño del formato de punto flotante: la diferencia entre los componentes de mantisa en las representaciones enteras de dos valores de punto flotante indica el número de posibles valores de punto flotante que separa los dos valores. Por ejemplo, la diferencia entre 0,0 y Epsilon es 1, porque Epsilon es el valor representable más pequeño al trabajar con un Single cuyo valor es cero. En el ejemplo siguiente se usa esta técnica para comparar .33333 y 1/3, que son los dos Double valores que el ejemplo de código anterior con el Equals(Single) método encontró que no es igual. Tenga en cuenta que en el ejemplo se usan los BitConverter.GetBytes métodos y BitConverter.ToInt32 para convertir un valor de punto flotante de precisión sencilla en su representación entera.

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("{0:R} = {1:R}: {2}", value1, value2,
                        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

La precisión de los números de punto flotante más allá de la precisión documentada es específica de la implementación y la versión de .NET. Por lo tanto, una comparación de dos números podría generar resultados diferentes en función de la versión de .NET, ya que la precisión de la representación interna de los números podría cambiar.