System.Single.Equals, metoda

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Metoda Single.Equals(Single) implementuje System.IEquatable<T> interfejs i działa nieco lepiej niż Single.Equals(Object) dlatego, że nie musi konwertować parametru obj na obiekt.

Konwersje rozszerzające

W zależności od języka programowania można kodować metodę Equals , w której typ parametru ma mniej bitów (jest węższy) niż typ wystąpienia. To jest możliwe, ponieważ w niektórych językach programowania jest wykonywana niejawna konwersja poszerzająca, która powoduje reprezentowanie parametru jako typu z taką samą liczbą bitów jak liczba bitów wystąpienia.

Załóżmy na przykład, że typ wystąpienia to Single , a typ parametru to Int32. Kompilator języka Microsoft C# generuje instrukcje reprezentujące wartość parametru jako Single obiekt, a następnie generuje metodę Single.Equals(Single) , która porównuje wartości wystąpienia i poszerzone reprezentacje parametru.

Sprawdź dokumentację języka programowania, aby określić, czy jego kompilator wykonuje niejawne poszerzenia konwersji dla typów liczbowych. Aby uzyskać więcej informacji, zobacz Tabele konwersji typów.

Precyzja w porównaniach

Metoda Equals powinna być używana z ostrożnością, ponieważ dwie pozornie równoważne wartości mogą być nierówne ze względu na różnicową precyzję tych dwóch wartości. Poniższy przykład zgłasza, że Single wartość .3333 i Single zwrócona przez podzielenie 1 przez 3 są nierówne.

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

Jedna technika porównania, która pozwala uniknąć problemów związanych z porównywaniem równości, polega na zdefiniowaniu dopuszczalnego marginesu różnicy między dwiema wartościami (na przykład 01% jednej z wartości). Jeśli wartość bezwzględna różnicy między dwiema wartościami jest mniejsza lub równa temu marginesowi, różnica może być wynikiem różnic w precyzji i dlatego wartości mogą być równe. W poniższym przykładzie użyto tej techniki do porównania wartości .33333 i 1/3, które są dwiema Single wartościami, które zostały uznane za nierówne w poprzednim przykładzie kodu.

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

W takim przypadku wartości są równe.

Uwaga

Ponieważ Epsilon definiuje minimalne wyrażenie wartości dodatniej, której zakres jest zbliżony do zera, margines różnicy musi być większy niż Epsilon. Zazwyczaj wartość jest wielokrotnie większa niż Epsilon. W związku z tym zalecamy, aby nie używać Epsilon ich podczas porównywania Double wartości pod kątem równości.

Druga technika, która pozwala uniknąć problemów związanych z porównywaniem równości, polega na porównaniu różnicy między dwiema liczbami zmiennoprzecinkowami z pewną wartością bezwzględną. Jeśli różnica jest mniejsza lub równa tej wartości bezwzględnej, liczby są równe. Jeśli jest większa, liczby nie są równe. Jednym ze sposobów wykonania tej czynności jest arbitralne wybranie wartości bezwzględnej. Jest to jednak problematyczne, ponieważ akceptowalny margines różnicy zależy od wielkości Single wartości. Drugi sposób wykorzystuje funkcję projektową formatu zmiennoprzecinkowego: Różnica między składnikami mantissa w reprezentacjach liczb całkowitych dwóch wartości zmiennoprzecinkowych wskazuje liczbę możliwych wartości zmiennoprzecinkowych, które oddziela dwie wartości. Na przykład różnica między wartością 0,0 a Epsilon jest równa 1, ponieważ Epsilon jest najmniejszą wartością reprezentującą podczas pracy z wartością, której Single wartość to zero. W poniższym przykładzie użyto tej techniki do porównania wartości .33333 i 1/3, które są dwiema Double wartościami, które zostały opisane w poprzednim przykładzie kodu z Equals(Single) metodą, która okazała się nierówna. Zwróć uwagę, że w przykładzie użyto BitConverter.GetBytes metod i BitConverter.ToInt32 w celu przekonwertowania wartości zmiennoprzecinkowej o pojedynczej precyzji na reprezentację liczb całkowitych.

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

Precyzja liczb zmiennoprzecinkowych poza udokumentowaną precyzją jest specyficzna dla implementacji i wersji platformy .NET. W związku z tym porównanie dwóch liczb może generować różne wyniki w zależności od wersji platformy .NET, ponieważ precyzja wewnętrznej reprezentacji liczb może ulec zmianie.