Метод System.Single.Equals

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Метод Single.Equals(Single) реализует System.IEquatable<T> интерфейс и выполняет немного лучше, чем Single.Equals(Object) из-за того, что он не должен преобразовывать obj параметр в объект.

Расширение преобразований

В зависимости от языка программирования можно закодировать Equals метод, в котором тип параметра имеет меньше битов (является более узким), чем тип экземпляра. Это возможно, так как некоторые языки программирования выполняют неявное расширение преобразования, представляющее параметр как тип с большим количеством битов, как экземпляр.

Например, предположим, что тип экземпляра имеет тип Single , а тип параметра — Int32. Компилятор Microsoft C# создает инструкции для представления значения параметра в качестве Single объекта, а затем создает Single.Equals(Single) метод, который сравнивает значения экземпляра и расширенный представление параметра.

Ознакомьтесь с документацией по языку программирования, чтобы определить, выполняет ли компилятор неявное расширение преобразований числовых типов. Дополнительные сведения см. в таблицах преобразования типов.

Точность сравнения

Метод Equals следует использовать с осторожностью, так как два по-видимому эквивалентных значения могут быть неравными из-за разной точности двух значений. В следующем примере сообщается, что Single значение .3333 и Single возвращаемое путем деления 1 на 3 не равны.

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

Один метод сравнения, который избегает проблем, связанных с сравнением равенства, включает определение допустимого поля разницы между двумя значениями (например, 01% одного из значений). Если абсолютное значение разницы между двумя значениями меньше или равно этому поле, разница, скорее всего, будет результатом различий в точности и, следовательно, значения, скорее всего, будут равны. В следующем примере используется этот метод для сравнения .333333 и 1/3, которые являются двумя Single значениями, которые предыдущий пример кода обнаружил, что неравный.

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

В этом случае значения равны.

Примечание.

Поскольку Epsilon определяет минимальное выражение положительного значения, диапазон которого близок к нулю, поле разницы должно быть больше Epsilon. Как правило, это много раз больше Epsilon. Из-за этого рекомендуется не использовать Epsilon при сравнении Double значений для равенства.

Второй метод, который избегает проблем, связанных с сравнением равенства, включает сравнение разницы между двумя числами с плавающей запятой с некоторым абсолютным значением. Если разница меньше или равна абсолютному значению, числа равны. Если это больше, числа не равны. Один из способов сделать это — произвольно выбрать абсолютное значение. Однако это проблематично, так как допустимое поле разницы зависит от величины значений Single . Второй способ использует функцию конструктора формата с плавающей запятой: разница между компонентами мантисса в целых представлениях двух значений с плавающей запятой указывает количество возможных значений с плавающей запятой, разделяющих два значения. Например, разница между 0,0 и Epsilon 1, так как Epsilon это наименьшее представляющее значение при работе с Single нулевым значением. В следующем примере используется этот метод для сравнения .333333 и 1/3, которые являются двумя Double значениями, которые были определены в предыдущем примере кода с Equals(Single) методом, равным неравным. Обратите внимание, что в примере используются BitConverter.GetBytes методы и BitConverter.ToInt32 методы для преобразования значения с плавающей запятой с одной точностью в целочисленное представление.

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

Точность чисел с плавающей запятой за пределами документированной точности зависит от реализации и версии .NET. Следовательно, сравнение двух чисел может привести к разным результатам в зависимости от версии .NET, так как точность внутреннего представления чисел может измениться.