System.Single.Equals-Metod

Anmärkning

Den här artikeln innehåller ytterligare kommentarer till referensdokumentationen för det här API:et.

Metoden Single.Equals(Single) implementerar System.IEquatable<T> gränssnittet och presterar något bättre än Single.Equals(Object) eftersom den inte behöver konvertera parametern obj till ett objekt.

Bredda konverteringar

Beroende på programmeringsspråket kan det vara möjligt att koda en Equals metod där parametertypen har färre bitar (är smalare) än instanstypen. Detta är möjligt eftersom vissa programmeringsspråk utför en implicit breddningskonvertering som representerar parametern som en typ med så många bitar som instansen.

Anta till exempel att instanstypen är Single och att parametertypen är Int32. Microsoft C#-kompilatorn genererar instruktioner för att representera värdet för parametern som ett Single objekt och genererar sedan en Single.Equals(Single) metod som jämför instansens värden och den utvidgade representationen av parametern.

Läs dokumentationen för programmeringsspråket för att avgöra om kompilatorn utför implicita breddningskonverteringar av numeriska typer. Mer information finns i Type Conversion Tables (Typkonverteringstabeller).

Precision i jämförelser

Metoden Equals bör användas med försiktighet, eftersom två till synes likvärdiga värden kan vara olika på grund av de två värdenas olika precision. Följande exempel rapporterar att Single-värdet .3333 och Single som returneras genom att dividera 1 med 3 inte är lika.

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

En jämförelseteknik som undviker de problem som är kopplade till likhetsjämförelse är att definiera en godtagbar skillnadsmarginal mellan två värden (till exempel .01% av ett av värdena). Om det absoluta värdet för skillnaden mellan de två värdena är mindre än eller lika med den marginalen är skillnaden sannolikt ett resultat av skillnader i precision och därför är värdena sannolikt lika. I följande exempel används den här tekniken för att jämföra .33333 och 1/3, som är de två Single värden som det tidigare kodexemplet visade sig vara ojämlika.

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

I det här fallet är värdena lika.

Anmärkning

Eftersom Epsilon definierar det minsta uttrycket för ett positivt värde vars intervall är nära noll, måste skillnadens marginal vara större än Epsilon. Vanligtvis är den många gånger större än Epsilon. Därför rekommenderar vi att du inte använder Epsilon när du jämför Double värden för likhet.

En andra teknik som undviker de problem som är kopplade till jämförelse för likhet är att jämföra skillnaden mellan två flyttalsnummer med något absolut värde. Om skillnaden är mindre än eller lika med det absoluta värdet är talen lika med. Om antalet är större så är de två talen inte lika. Ett sätt att göra detta är att godtyckligt välja ett absolut värde. Detta är dock problematiskt eftersom en acceptabel skillnadsmarginal beror på värdenas Single storlek. Ett andra sätt att dra nytta av en designfunktion i flyttalsformatet: Skillnaden mellan mantissakomponenterna i heltalsrepresentationerna av två flyttalsvärden anger antalet möjliga flyttalsvärden som separerar de två värdena. Till exempel är skillnaden mellan 0,0 och Epsilon 1, eftersom Epsilon är det minsta representerande värdet när du arbetar med en Single vars värde är noll. I följande exempel används den här tekniken för att jämföra .33333 och 1/3, vilket är de två Double värden som det tidigare kodexemplet med Equals(Single) metoden visade sig vara ojämlika. Observera att exemplet använder BitConverter.GetBytes- och BitConverter.ToInt32-metoderna för att konvertera ett flyttal med enkel precision till dess heltalsrepresentation.

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

Precisionen för flyttalsnummer utöver den dokumenterade precisionen är specifik för implementeringen och versionen av .NET. Därför kan en jämförelse av två tal ge olika resultat beroende på versionen av .NET, eftersom precisionen i talens interna representation kan ändras.