Метод 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 равное 0.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% одного из значений). Если абсолютное значение разницы между двумя значениями меньше или равно этой границе, разница, скорее всего, связана с различиями в точности и, следовательно, значения вероятно равны. В следующем примере используется этот метод для сравнения .33333 и 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, у которого значение равно нулю. В следующем примере используется этот метод для сравнения .33333 и 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($"{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
Точность чисел с плавающей запятой за пределами документированной точности зависит от реализации и версии .NET. Следовательно, сравнение двух чисел может привести к разным результатам в зависимости от версии .NET, так как точность внутреннего представления чисел может измениться.