Поделиться через


Метод System.Double.Equals

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

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

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

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

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

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

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

// Initialize two doubles with apparently identical values
double double1 = .33333;
double double2 = (double) 1/3;
// Compare them for equality
Console.WriteLine(double1.Equals(double2));    // displays false
// Initialize two doubles with apparently identical values
let double1 = 0.33333
let double2 = double (1 / 3)
// Compare them for equality
printfn $"{double1.Equals double2}"    // displays false
' Initialize two doubles with apparently identical values
Dim double1 As Double = .33333
Dim double2 As Double = 1/3
' Compare them for equality
Console.WriteLine(double1.Equals(double2))    ' displays False

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

// Initialize two doubles with apparently identical values
double double1 = .333333;
double double2 = (double) 1/3;
// Define the tolerance for variation in their values
double difference = Math.Abs(double1 * .00001);

// Compare the values
// The output to the console indicates that the two values are equal
if (Math.Abs(double1 - double2) <= difference)
   Console.WriteLine("double1 and double2 are equal.");
else
   Console.WriteLine("double1 and double2 are unequal.");
// Initialize two doubles with apparently identical values
let double1 = 0.333333
let double2 = double (1 / 3)
// Define the tolerance for variation in their values
let difference = abs (double1 * 0.00001)

// Compare the values
// The output to the console indicates that the two values are equal
if abs (double1 - double2) <= difference then
    printfn "double1 and double2 are equal."
else
    printfn "double1 and double2 are unequal."
' Initialize two doubles with apparently identical values
Dim double1 As Double = .33333
Dim double2 As Double = 1/3
' Define the tolerance for variation in their values
Dim difference As Double = Math.Abs(double1 * .00001)

' Compare the values
' The output to the console indicates that the two values are equal
If Math.Abs(double1 - double2) <= difference Then
   Console.WriteLine("double1 and double2 are equal.")
Else
   Console.WriteLine("double1 and double2 are unequal.")
End If

Примечание.

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

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

public static void Main()
{
    // Initialize the values.
    double value1 = .1 * 10;
    double value2 = 0;
    for (int ctr = 0; ctr < 10; ctr++)
        value2 += .1;

    Console.WriteLine($"{value1:R} = {value2:R}: " +
        $"{HasMinimalDifference(value1, value2, 1)}");
}

public static bool HasMinimalDifference(
    double value1,
    double value2,
    int allowableDifference
    )
{
    // Convert the double values to long values.
    long lValue1 = BitConverter.DoubleToInt64Bits(value1);
    long lValue2 = BitConverter.DoubleToInt64Bits(value2);

    // If the signs are different, return false except for +0 and -0.
    if ((lValue1 >> 63) != (lValue2 >> 63))
    {
        if (value1 == value2)
            return true;

        return false;
    }

    // Calculate the number of possible
    // floating-point values in the difference.
    long diff = Math.Abs(lValue1 - lValue2);

    if (diff <= allowableDifference)
        return true;

    return false;
}
// The example displays the following output:
//
//        1 = 0.99999999999999989: True
open System

let hasMinimalDifference (value1: double) (value2: double) (units: int) =
    let lValue1 = BitConverter.DoubleToInt64Bits value1
    let lValue2 = BitConverter.DoubleToInt64Bits value2

    // If the signs are different, return false except for +0 and -0.
    if (lValue1 >>> 63) <> (lValue2 >>> 63) then
        value1 = value2
    else
        let diff = abs (lValue1 - lValue2)

        diff <= int64 units

let value1 = 0.1 * 10.
let mutable value2 = 0.
for _ = 0 to 9 do
    value2 <- value2 + 0.1

printfn $"{value1:R} = {value2:R}: {hasMinimalDifference value1 value2 1}"
                

// The example displays the following output:
//        1 = 0.99999999999999989: True
Module Example
   Public Sub Main()
      Dim value1 As Double = .1 * 10
      Dim value2 As Double = 0
      For ctr As Integer =  0 To 9
         value2 += .1
      Next
               
      Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2,
                        HasMinimalDifference(value1, value2, 1))
   End Sub

   Public Function HasMinimalDifference(value1 As Double, value2 As Double, units As Integer) As Boolean
      Dim lValue1 As long =  BitConverter.DoubleToInt64Bits(value1)
      Dim lValue2 As long =  BitConverter.DoubleToInt64Bits(value2)
      
      ' If the signs are different, Return False except for +0 and -0.
      If ((lValue1 >> 63) <> (lValue2 >> 63)) Then
         If value1 = value2 Then
            Return True
         End If           
         Return False
      End If

      Dim diff As Long =  Math.Abs(lValue1 - lValue2)

      If diff <= units Then
         Return True
      End If

      Return False
   End Function
End Module
' The example displays the following output:
'       1 = 0.99999999999999989: True

Примечание.

Для некоторых значений их можно считать равными даже при наличии возможного значения с плавающей запятой между целыми представлениями. Например, рассмотрим двойные значения 0.39 и 1.69 - 1.3 (вычисляется как 0.3899999999999999). На маленьком компьютере целочисленные представления этих значений и 4600697235336603894 4600697235336603892соответственно. Разница между целыми значениями заключается в 2том, что существует возможное значение с плавающей запятой между 0.39 и 1.69 - 1.3.

Различия версий

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

Не число

Если два Double.NaN значения проверяются на равенство путем вызова Equals метода, метод возвращается true. Однако если два Double.NaN значения проверяются на равенство с помощью оператора равенства, то оператор возвращается false. Если вы хотите определить, является ли значение Double не числом (NaN), альтернативой является вызов IsNaN метода.