Свойство System.Double.Epsilon

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

Значение Epsilon свойства отражает наименьшее положительное Double значение, которое имеет значительное значение в числовых операциях или сравнениях, если значение Double экземпляра равно нулю. Например, в следующем коде показано, что ноль и Epsilon считаются неравными значениями, в то время как ноль и половина значения Epsilon считаются равными.

using System;

public class Example
{
   public static void Main()
   {
      double[] values = { 0, Double.Epsilon, Double.Epsilon * .5 };

      for (int ctr = 0; ctr <= values.Length - 2; ctr++)
      {
         for (int ctr2 = ctr + 1; ctr2 <= values.Length - 1; ctr2++)
         {
            Console.WriteLine("{0:r} = {1:r}: {2}",
                              values[ctr], values[ctr2],
                              values[ctr].Equals(values[ctr2]));
         }
         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       0 = 4.94065645841247E-324: False
//       0 = 0: True
//
//       4.94065645841247E-324 = 0: False
open System

let values = [| 0.; Double.Epsilon; Double.Epsilon * 0.5 |]

for i = 0 to values.Length - 2 do
    for i2 = i + 1 to values.Length - 1 do
        printfn $"{values[i]:r} = {values[i2]:r}: {values[i].Equals values[i2]}"
    printfn ""
// The example displays the following output:
//       0 = 4.94065645841247E-324: False
//       0 = 0: True
//
//       4.94065645841247E-324 = 0: False
Module Example
   Public Sub Main()
      Dim values() As Double = { 0, Double.Epsilon, Double.Epsilon * .5 }
      
      For ctr As Integer = 0 To values.Length - 2
         For ctr2 As Integer = ctr + 1 To values.Length - 1
            Console.WriteLine("{0:r} = {1:r}: {2}", _
                              values(ctr), values(ctr2), _ 
                              values(ctr).Equals(values(ctr2)))
         Next
         Console.WriteLine()
      Next      
   End Sub
End Module
' The example displays the following output:
'       0 = 4.94065645841247E-324: False
'       0 = 0: True
'       
'       4.94065645841247E-324 = 0: False

Точнее, формат с плавающей запятой состоит из знака, 52-разрядной мантиссы или знака и 11-разрядной экспоненты. Как показано в следующем примере, ноль имеет экспонент -1022 и мантисса 0. Epsilon имеет экспонент -1022 и мантисса 1. Это означает, что Epsilon наименьшее положительное Double значение больше нуля и представляет наименьшее возможное значение и наименьшее возможное увеличение для Double экспоненты которого равно -1022.

using System;

public class Example1
{
    public static void Main()
    {
        double[] values = { 0.0, Double.Epsilon };
        foreach (var value in values)
        {
            Console.WriteLine(GetComponentParts(value));
            Console.WriteLine();
        }
    }

    private static string GetComponentParts(double value)
    {
        string result = String.Format("{0:R}: ", value);
        int indent = result.Length;

        // Convert the double to an 8-byte array.
        byte[] bytes = BitConverter.GetBytes(value);
        // Get the sign bit (byte 7, bit 7).
        result += String.Format("Sign: {0}\n",
                                (bytes[7] & 0x80) == 0x80 ? "1 (-)" : "0 (+)");

        // Get the exponent (byte 6 bits 4-7 to byte 7, bits 0-6)
        int exponent = (bytes[7] & 0x07F) << 4;
        exponent = exponent | ((bytes[6] & 0xF0) >> 4);
        int adjustment = exponent != 0 ? 1023 : 1022;
        result += String.Format("{0}Exponent: 0x{1:X4} ({1})\n", new String(' ', indent), exponent - adjustment);

        // Get the significand (bits 0-51)
        long significand = ((bytes[6] & 0x0F) << 48);
        significand = significand | ((long)bytes[5] << 40);
        significand = significand | ((long)bytes[4] << 32);
        significand = significand | ((long)bytes[3] << 24);
        significand = significand | ((long)bytes[2] << 16);
        significand = significand | ((long)bytes[1] << 8);
        significand = significand | bytes[0];
        result += String.Format("{0}Mantissa: 0x{1:X13}\n", new String(' ', indent), significand);

        return result;
    }
}
//       // The example displays the following output:
//       0: Sign: 0 (+)
//          Exponent: 0xFFFFFC02 (-1022)
//          Mantissa: 0x0000000000000
//
//
//       4.94065645841247E-324: Sign: 0 (+)
//                              Exponent: 0xFFFFFC02 (-1022)
//                              Mantissa: 0x0000000000001
open System

let getComponentParts (value: double) =
    let result = $"{value:R}: "
    let indent = result.Length

    // Convert the double to an 8-byte array.
    let bytes = BitConverter.GetBytes value
    // Get the sign bit (byte 7, bit 7).
    let result = result + $"""Sign: {if (bytes[7] &&& 0x80uy) = 0x80uy then "1 (-)" else "0 (+)"}\n"""

    // Get the exponent (byte 6 bits 4-7 to byte 7, bits 0-6)
    let exponent = (bytes[7] &&& 0x07Fuy) <<< 4
    let exponent = exponent ||| ((bytes[6] &&& 0xF0uy) >>> 4)
    let adjustment = if exponent <> 0uy then 1022 else 1023
    let result = result + $"{String(' ', indent)}Exponent: 0x{int exponent - adjustment:X4} ({int exponent - adjustment})\n"

    // Get the significand (bits 0-51)
    let significand = (bytes[6] &&& 0x0Fuy) <<< 48
    let significand = significand ||| byte (int64 bytes[5] <<< 40)
    let significand = significand ||| byte (int64 bytes[4] <<< 32)
    let significand = significand ||| byte (int64 bytes[3] <<< 24)
    let significand = significand ||| byte (int64 bytes[2] <<< 16)
    let significand = significand ||| byte (int64 bytes[1] <<< 8)
    let significand = significand ||| bytes[0]
    result + $"{String(' ', indent)}Mantissa: 0x{significand:X13}\n"

let values = [| 0.; Double.Epsilon |]
for value in values do
   printfn $"{getComponentParts value}"
   printfn ""


//       // The example displays the following output:
//       0: Sign: 0 (+)
//          Exponent: 0xFFFFFC02 (-1022)
//          Mantissa: 0x0000000000000
//
//
//       4.94065645841247E-324: Sign: 0 (+)
//                              Exponent: 0xFFFFFC02 (-1022)
//                              Mantissa: 0x0000000000001
Module Example1
   Public Sub Main()
      Dim values() As Double = { 0.0, Double.Epsilon }
      For Each value In values
         Console.WriteLine(GetComponentParts(value))
         Console.WriteLine()
      Next
   End Sub

   Private Function GetComponentParts(value As Double) As String
      Dim result As String =  String.Format("{0:R}: ", value)
      Dim indent As Integer =  result.Length

      ' Convert the double to an 8-byte array.
      Dim bytes() As Byte = BitConverter.GetBytes(value)
      ' Get the sign bit (byte 7, bit 7).
      result += String.Format("Sign: {0}{1}",
                              If((bytes(7) And &H80) = &H80, "1 (-)", "0 (+)"),
                              vbCrLf)

      ' Get the exponent (byte 6 bits 4-7 to byte 7, bits 0-6)
      Dim exponent As Integer =  (bytes(7) And &H07F) << 4
      exponent = exponent Or ((bytes(6) And &HF0) >> 4)
      Dim adjustment As Integer = If(exponent <> 0, 1023, 1022)
      result += String.Format("{0}Exponent: 0x{1:X4} ({1}){2}",
                              New String(" "c, indent), exponent - adjustment,
                              vbCrLf)

      ' Get the significand (bits 0-51)
      Dim significand As Long =  ((bytes(6) And &H0F) << 48)
      significand = significand Or (bytes(5) << 40)
      significand = significand Or (bytes(4) << 32)
      significand = significand Or (bytes(3) << 24)
      significand = significand Or (bytes(2) << 16)
      significand = significand Or (bytes(1) << 8)
      significand = significand Or bytes(0)
      result += String.Format("{0}Mantissa: 0x{1:X13}{2}",
                              New String(" "c, indent), significand, vbCrLf)

      Return result
   End Function
End Module
' The example displays the following output:
'       0: Sign: 0 (+)
'          Exponent: 0xFFFFFC02 (-1022)
'          Mantissa: 0x0000000000000
'
'
'       4.94065645841247E-324: Sign: 0 (+)
'                              Exponent: 0xFFFFFC02 (-1022)
'                              Mantissa: 0x0000000000001

Epsilon Однако свойство не является общей мерой точности Double типа, она применяется только к Double экземплярам, имеющим значение нуля или экспонента -1022.

Примечание.

Значение Epsilon свойства не эквивалентно эпсилону компьютера, представляющего верхнюю границу относительной ошибки из-за округления в арифметике с плавающей запятой.

Значение этой константы равно 4.94065645841247e-324.

Два, по-видимому, эквивалентные числа с плавающей запятой, могут не сравниваться из-за различий в их наименее значимых цифрах. Например, выражение (double)1/3 == (double)0.33333C# не сравнивает равный, так как операция деления на левой стороне имеет максимальную точность, а константу на правой стороне точно совпадают только с указанными цифрами. Если вы создаете пользовательский алгоритм, определяющий, можно ли считать равными два числа с плавающей запятой, мы не рекомендуем использовать алгоритм на значении Epsilon константы, чтобы установить допустимое абсолютное поле разницы для двух значений, которые будут считаться равными. (Как правило, это поле разницы во многих раз больше, чем Epsilon.) Сведения о сравнении двух значений с плавающей запятой двойной точности см. в разделе Double и Equals(Double).

Заметки о платформе

В системах ARM значение Epsilon константы слишком мало для обнаружения, поэтому оно равно нулю. Вместо этого можно определить альтернативное значение epsilon, равное 2.2250738585072014E-308.