Метод 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, так как точность внутреннего представления чисел может измениться.