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


Структура System.Single

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

Тип значения Single представляет одноточное 32-разрядное число со значениями от отрицательного 3,402823e38 до положительного 3,402823e38, а также положительного или отрицательного нуля, PositiveInfinity, NegativeInfinity, а не числа (NaN). Оно предназначено для представления значений, которые чрезвычайно большие (например, расстояния между планетами или галактиками) или крайне малы (например, молекулярной массы вещества в килограммах) и которые часто являются неуловкими (например, расстояние от земли к другой солнечной системе). Тип Single соответствует стандарту IEC 60559:1989 (IEEE 754) для арифметики с плавающей запятой.

System.Single предоставляет методы для сравнения экземпляров этого типа, преобразования значения экземпляра в его строковое представление и преобразования строкового представления числа в экземпляр этого типа. Сведения о том, как коды спецификаций формата управляют строковым представлением типов значений, см. в типах форматирования, строках стандартного числового форматаи строк пользовательского числового формата.

Представление и точность с плавающей запятой

Тип данных Single сохраняет значения с плавающей запятой с одной точностью в 32-разрядном двоичном формате, как показано в следующей таблице:

Часть Биты
Significand или мантисса 0-22
Показатель 23-30
Знак (0 = положительный, 1 = отрицательный) 31

Так же, как десятичные дроби не могут точно представлять некоторые дробные значения (например, 1/3 или Math.PI), двоичные дроби не могут представлять некоторые дробные значения. Например, 2/10, которое представлено как 0.2 в виде десятичной дроби, представлено как 0.0011111001001100 в виде двоичной дроби, при этом цикл "1100" повторяется до бесконечности. В этом случае значение с плавающей запятой дает неточное представление о числе, которое оно обозначает. Выполнение дополнительных математических операций с исходным значением с плавающей запятой часто увеличивает его отсутствие точности. Например, если сравнить результаты умножения .3 на 10 и добавления .3 к .3 девять раз, вы увидите, что добавление создает менее точный результат, так как он включает в себя восемь больше операций, чем умножение. Обратите внимание, что это различие очевидно, только если вы отображаете оба значения Single, используя строку стандартного числового формата "R" , которая при необходимости отображает все 9 цифр точности, поддерживаемых типом Single.

using System;

public class Example12
{
    public static void Main()
    {
        Single value = .2f;
        Single result1 = value * 10f;
        Single result2 = 0f;
        for (int ctr = 1; ctr <= 10; ctr++)
            result2 += value;

        Console.WriteLine($".2 * 10:           {result1:R}");
        Console.WriteLine($".2 Added 10 times: {result2:R}");
    }
}
// The example displays the following output:
//       .2 * 10:           2
//       .2 Added 10 times: 2.00000024
let value = 0.2f
let result1 = value * 10f
let mutable result2 = 0f
for _ = 1 to 10 do
    result2 <- result2 + value

printfn $".2 * 10:           {result1:R}"
printfn $".2 Added 10 times: {result2:R}"
// The example displays the following output:
//       .2 * 10:           2
//       .2 Added 10 times: 2.00000024
Module Example13
    Public Sub Main()
        Dim value As Single = 0.2
        Dim result1 As Single = value * 10
        Dim result2 As Single
        For ctr As Integer = 1 To 10
            result2 += value
        Next
        Console.WriteLine(".2 * 10:           {0:R}", result1)
        Console.WriteLine(".2 Added 10 times: {0:R}", result2)
    End Sub
End Module
' The example displays the following output:
'       .2 * 10:           2
'       .2 Added 10 times: 2.00000024

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

Все числа с плавающей запятой имеют ограниченное количество значимых цифр, которое также определяет, насколько точно значение с плавающей запятой приближается к реальному числу. Значение Single имеет до 7 десятичных цифр точности, хотя внутренне поддерживается максимальное количество в 9 цифр. Это означает, что некоторые операции с плавающей запятой могут недоставать точности для изменения значения с плавающей запятой. В следующем примере определяется большое число с плавающей запятой с одинарной точностью, а затем к нему добавляется произведение Single.Epsilon и одного квадриллиона. Однако результат умножения слишком мал, чтобы изменить исходное значение с плавающей запятой. Его наименьшая значимая цифра — тысячные, в то время как наиболее значимая цифра в продукте составляет 10–30.

using System;

public class Example13
{
    public static void Main()
    {
        Single value = 123.456f;
        Single additional = Single.Epsilon * 1e15f;
        Console.WriteLine($"{value} + {additional} = {value + additional}");
    }
}
// The example displays the following output:
//    123.456 + 1.401298E-30 = 123.456
open System

let value = 123.456f
let additional = Single.Epsilon * 1e15f
printfn $"{value} + {additional} = {value + additional}"
// The example displays the following output:
//    123.456 + 1.401298E-30 = 123.456
Module Example
   Public Sub Main()
      Dim value As Single = 123.456
      Dim additional As Single = Single.Epsilon * 1e15
      Console.WriteLine($"{value} + {additional} = {value + additional}")
   End Sub
End Module
' The example displays the following output:
'   123.456 + 1.401298E-30 = 123.456

Ограниченная точность числа с плавающей запятой имеет несколько последствий:

  • Два числа с плавающей запятой, которые, как представляется, равны для определенной точности, могут не сравниться, так как их наименьшие значимые цифры отличаются. В следующем примере ряд чисел добавляется вместе, и их общее значение сравнивается с ожидаемым итогом. Хотя два значения, как представляется, одинаковы, вызов метода Equals указывает, что они не равны.

    using System;
    
    public class Example9
    {
        public static void Main()
        {
            Single[] values = { 10.01f, 2.88f, 2.88f, 2.88f, 9.0f };
            Single result = 27.65f;
            Single total = 0f;
            foreach (var value in values)
                total += value;
    
            if (total.Equals(result))
                Console.WriteLine("The sum of the values equals the total.");
            else
                Console.WriteLine($"The sum of the values ({total}) does not equal the total ({result}).");
        }
    }
    // The example displays the following output:
    //      The sum of the values (27.65) does not equal the total (27.65).   
    //
    // If the index items in the Console.WriteLine statement are changed to {0:R},
    // the example displays the following output:
    //       The sum of the values (27.6500015) does not equal the total (27.65).
    
    let values = [| 10.01f; 2.88f; 2.88f; 2.88f; 9f |]
    let result = 27.65f
    let mutable total = 0f
    for value in values do
        total <- total + value
    
    if total.Equals result then
        printfn "The sum of the values equals the total."
    else
        printfn "The sum of the values ({total}) does not equal the total ({result})."
    // The example displays the following output:
    //      The sum of the values (27.65) does not equal the total (27.65).   
    //
    // If the index items in the Console.WriteLine statement are changed to {0:R},
    // the example displays the following output:
    //       The sum of the values (27.6500015) does not equal the total (27.65).
    
    Module Example10
        Public Sub Main()
            Dim values() As Single = {10.01, 2.88, 2.88, 2.88, 9.0}
            Dim result As Single = 27.65
            Dim total As Single
            For Each value In values
                total += value
            Next
            If total.Equals(result) Then
                Console.WriteLine("The sum of the values equals the total.")
            Else
                Console.WriteLine("The sum of the values ({0}) does not equal the total ({1}).",
                               total, result)
            End If
        End Sub
    End Module
    ' The example displays the following output:
    '      The sum of the values (27.65) does not equal the total (27.65).   
    '
    ' If the index items in the Console.WriteLine statement are changed to {0:R},
    ' the example displays the following output:
    '       The sum of the values (27.639999999999997) does not equal the total (27.64).
    

    Если изменить элементы формата в инструкции Console.WriteLine(String, Object, Object) с {0} и {1} на {0:R} и {1:R}, чтобы отобразить все значимые цифры двух значений Single, ясно, что эти два значения не равны из-за потери точности во время операций сложения. В этом случае проблему можно устранить, вызвав метод Math.Round(Double, Int32) для округления значений Single до требуемой точности перед выполнением сравнения.

  • Математические операции или операции сравнения, использующие число с плавающей запятой, могут не дать тот же результат, если используется десятичное число, так как двоичное число с плавающей запятой может не совпадать с десятичным числом. Предыдущий пример иллюстрировал это, отображая результат умножения .3 на 10 и прибавления .3 к .3 девять раз подряд.

    Если точность числовых операций с дробными значениями важна, используйте тип Decimal вместо типа Single. Если точность в числовых операциях со целыми значениями за пределами диапазона типов Int64 или UInt64 важна, используйте тип BigInteger.

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

    В следующем примере три значения Single преобразуются в строки и сохраняются в файле. Если вы запускаете этот пример в .NET Framework, хотя значения, как представляется, идентичны, восстановленные значения не равны исходным значениям. (Это с тех пор было устранено в .NET, где значения корректно возвращаются.)

    StreamWriter sw = new(@"./Singles.dat");
    float[] values = { 3.2f / 1.11f, 1.0f / 3f, (float)Math.PI };
    for (int ctr = 0; ctr < values.Length; ctr++)
    {
        sw.Write(values[ctr].ToString());
        if (ctr != values.Length - 1)
            sw.Write("|");
    }
    sw.Close();
    
    float[] restoredValues = new float[values.Length];
    StreamReader sr = new(@"./Singles.dat");
    string temp = sr.ReadToEnd();
    string[] tempStrings = temp.Split('|');
    for (int ctr = 0; ctr < tempStrings.Length; ctr++)
        restoredValues[ctr] = float.Parse(tempStrings[ctr]);
    
    for (int ctr = 0; ctr < values.Length; ctr++)
        Console.WriteLine($"{values[ctr]} {(values[ctr].Equals(restoredValues[ctr]) ? "=" : "<>")} {restoredValues[ctr]}");
    
    // The example displays the following output:
    //       2.882883 <> 2.882883
    //       0.3333333 <> 0.3333333
    //       3.141593 <> 3.141593
    
    open System
    open System.IO
    
    let values = [| 3.2f / 1.11f; 1f / 3f; MathF.PI |]
    
    do
        use sw = new StreamWriter(@".\Singles.dat")
        for i = 0 to values.Length - 1 do
            sw.Write(string values[i])
            if i <> values.Length - 1 then
                sw.Write "|"
    
    let restoredValues =
        use sr = new StreamReader(@".\Singles.dat")
        sr.ReadToEnd().Split '|'
        |> Array.map Single.Parse
    
    for i = 0 to values.Length - 1 do
        printfn $"""{values[i]} {if values[i].Equals restoredValues[i] then "=" else "<>"} {restoredValues[i]}"""
                        
    // The example displays the following output:
    //       2.882883 <> 2.882883
    //       0.3333333 <> 0.3333333
    //       3.141593 <> 3.141593
    
    Imports System.IO
    
    Module Example11
        Public Sub Main()
            Dim sw As New StreamWriter(".\Singles.dat")
            Dim values() As Single = {3.2 / 1.11, 1.0 / 3, CSng(Math.PI)}
            For ctr As Integer = 0 To values.Length - 1
                sw.Write(values(ctr).ToString())
                If ctr <> values.Length - 1 Then sw.Write("|")
            Next
            sw.Close()
    
            Dim restoredValues(values.Length - 1) As Single
            Dim sr As New StreamReader(".\Singles.dat")
            Dim temp As String = sr.ReadToEnd()
            Dim tempStrings() As String = temp.Split("|"c)
            For ctr As Integer = 0 To tempStrings.Length - 1
                restoredValues(ctr) = Single.Parse(tempStrings(ctr))
            Next
    
            For ctr As Integer = 0 To values.Length - 1
                Console.WriteLine("{0} {2} {1}", values(ctr),
                               restoredValues(ctr),
                               If(values(ctr).Equals(restoredValues(ctr)), "=", "<>"))
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '        2.882883 <> 2.882883
    '        0.3333333 <> 0.3333333
    '        3.141593 <> 3.141593
    

    Если вы нацелены на .NET Framework, значения можно успешно восстановить с помощью форматной строки "G9" стандартного числового формата, чтобы сохранить полную точность значений Single.

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

    using System;
    
    public class Example9
    {
        public static void Main()
        {
            Double value1 = 1 / 3.0;
            Single sValue2 = 1 / 3.0f;
            Double value2 = (Double)sValue2;
            Console.WriteLine($"{value1:R} = {value2:R}: {value1.Equals(value2)}");
        }
    }
    // The example displays the following output:
    //        0.33333333333333331 = 0.3333333432674408: False
    
    open System
    
    let value1 = 1. / 3.
    let sValue2 = 1f /3f
    
    let value2 = double sValue2
    printfn $"{value1:R} = {value2:R}: {value1.Equals value2}"
    // The example displays the following output:
    //        0.33333333333333331 = 0.3333333432674408: False
    
    Module Example10
        Public Sub Main()
            Dim value1 As Double = 1 / 3
            Dim sValue2 As Single = 1 / 3
            Dim value2 As Double = CDbl(sValue2)
            Console.WriteLine("{0} = {1}: {2}", value1, value2, value1.Equals(value2))
        End Sub
    End Module
    ' The example displays the following output:
    '       0.33333333333333331 = 0.3333333432674408: False
    

    Чтобы избежать этой проблемы, используйте Double тип данных вместо типа данных Single или используйте метод Round, чтобы оба значения имели одинаковую точность.

Проверка на равенство

Чтобы считаться равным, два значения Single должны представлять идентичные значения. Однако из-за различий в точности между значениями или из-за потери точности по одному или обоим значениям значения с плавающей запятой, которые, как ожидается, будут одинаковыми, часто оказываются неравными из-за различий в их наименее значимых цифрах. В результате вызовы метода Equals, чтобы определить, равны ли два значения, или вызовы метода CompareTo для определения связи между двумя значениями Single, часто дают непредвиденные результаты. Очевидно в следующем примере: два значения Single, казалось бы, равные, оказываются не равны, потому что первое значение имеет точность в 7 цифр, тогда как второе - в 9.

using System;

public class Example
{
   public static void Main()
   {
      float value1 = .3333333f;
      float value2 = 1.0f/3;
      Console.WriteLine($"{value1:R} = {value2:R}: {value1.Equals(value2)}");
   }
}
// The example displays the following output:
//        0.3333333 = 0.333333343: False
let value1 = 0.3333333f
let value2 = 1f / 3f
printfn $"{value1:R} = {value2:R}: {value1.Equals value2}"
// The example displays the following output:
//        0.3333333 = 0.333333343: False
Module Example1
    Public Sub Main()
        Dim value1 As Single = 0.3333333
        Dim value2 As Single = 1 / 3
        Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2, value1.Equals(value2))
    End Sub
End Module
' The example displays the following output:
'       0.3333333 = 0.333333343: False

Вычисляемые значения, которые следуют разным путям кода и которые управляются различными способами, часто оказываются неравными. В следующем примере одно значение Single возводится в квадрат, а затем вычисляется квадратный корень для получения исходного значения. Второй Single умножается на 3,51 и квадрат перед квадратным корнем результата делится на 3,51, чтобы восстановить исходное значение. Хотя два значения, как представляется, идентичны, вызов метода Equals(Single) указывает, что они не равны. При использовании стандартной строки формата G9 для возврата строки результата, отображающей все значимые цифры каждого значения Single, показано, что второе значение равно .0000000000001 меньше первого.

using System;

public class Example1
{
    public static void Main()
    {
        float value1 = 10.201438f;
        value1 = (float)Math.Sqrt((float)Math.Pow(value1, 2));
        float value2 = (float)Math.Pow((float)value1 * 3.51f, 2);
        value2 = ((float)Math.Sqrt(value2)) / 3.51f;
        Console.WriteLine($"{value1} = {value2}: {value1.Equals(value2)}");
        Console.WriteLine();
        Console.WriteLine($"{value1:G9} = {value2:G9}");
    }
}
// The example displays the following output:
//       10.20144 = 10.20144: False
//       
//       10.201438 = 10.2014389
let value1 = 
    10.201438f ** 2f
    |> sqrt

let value2 =
   ((value1 * 3.51f) ** 2f |> sqrt) / 3.51f

printfn $"{value1} = {value2}: {value1.Equals value2}\n" 
printfn $"{value1:G9} = {value2:G9}"
// The example displays the following output:
//       10.20144 = 10.20144: False
//       
//       10.201438 = 10.2014389
Module Example2
    Public Sub Main()
        Dim value1 As Single = 10.201438
        value1 = CSng(Math.Sqrt(CSng(Math.Pow(value1, 2))))
        Dim value2 As Single = CSng(Math.Pow(value1 * CSng(3.51), 2))
        value2 = CSng(Math.Sqrt(value2) / CSng(3.51))
        Console.WriteLine("{0} = {1}: {2}",
                        value1, value2, value1.Equals(value2))
        Console.WriteLine()
        Console.WriteLine("{0:G9} = {1:G9}", value1, value2)
    End Sub
End Module
' The example displays the following output:
'       10.20144 = 10.20144: False
'       
'       10.201438 = 10.2014389

В случаях, когда потеря точности, скорее всего, влияет на результат сравнения, можно использовать следующие методы вместо вызова метода Equals или CompareTo:

  • Вызовите метод Math.Round, чтобы убедиться, что оба значения имеют одинаковую точность. Следующий пример изменяет предыдущий пример для использования этого подхода таким образом, чтобы два дробных значения были эквивалентны.

    using System;
    
    public class Example2
    {
        public static void Main()
        {
            float value1 = .3333333f;
            float value2 = 1.0f / 3;
            int precision = 7;
            value1 = (float)Math.Round(value1, precision);
            value2 = (float)Math.Round(value2, precision);
            Console.WriteLine($"{value1:R} = {value2:R}: {value1.Equals(value2)}");
        }
    }
    // The example displays the following output:
    //        0.3333333 = 0.3333333: True
    
    open System
    
    let value1 = 0.3333333f
    let value2 = 1f / 3f
    let precision = 7
    let value1r = Math.Round(float value1, precision) |> float32
    let value2r = Math.Round(float value2, precision) |> float32
    printfn $"{value1:R} = {value2:R}: {value1.Equals value2}"
    // The example displays the following output:
    //        0.3333333 = 0.3333333: True
    
    Module Example3
        Public Sub Main()
            Dim value1 As Single = 0.3333333
            Dim value2 As Single = 1 / 3
            Dim precision As Integer = 7
            value1 = CSng(Math.Round(value1, precision))
            value2 = CSng(Math.Round(value2, precision))
            Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2, value1.Equals(value2))
        End Sub
    End Module
    ' The example displays the following output:
    '       0.3333333 = 0.3333333: True
    

    Проблема точности по-прежнему применяется к округлениям значений средней точки. Дополнительные сведения см. в методе Math.Round(Double, Int32, MidpointRounding).

  • Проверяйте приблизительное равенство вместо точного равенства. Для этого метода необходимо определить абсолютное значение, по которому эти два значения могут отличаться, но по-прежнему равны, или определить относительную сумму, по которой меньшее значение может отличаться от большего значения.

    Предупреждение

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

    В следующем примере используется последний подход для определения метода IsApproximatelyEqual, который проверяет относительную разницу между двумя значениями. Он также сравнивает результаты вызовов метода IsApproximatelyEqual и метода Equals(Single).

    using System;
    
    public class Example3
    {
        public static void Main()
        {
            float one1 = .1f * 10;
            float one2 = 0f;
            for (int ctr = 1; ctr <= 10; ctr++)
                one2 += .1f;
    
            Console.WriteLine($"{one1:R} = {one2:R}: {one1.Equals(one2)}");
            Console.WriteLine($"{one1:R} is approximately equal to {one2:R}: " +
                $"{IsApproximatelyEqual(one1, one2, .000001f)}");
            
            float negativeOne1 = -1 * one1;
            float negativeOne2 = -1 * one2;
    
            Console.WriteLine($"{negativeOne1:R} = {negativeOne2:R}: {negativeOne1.Equals(negativeOne2)}");
            Console.WriteLine($"{negativeOne1:R} is approximately equal to {negativeOne2:R}: " +
                $"{IsApproximatelyEqual(negativeOne1, negativeOne2, .000001f)}");
        }
    
        static bool IsApproximatelyEqual(float value1, float value2, float epsilon)
        {
            // If they are equal anyway, just return True.
            if (value1.Equals(value2))
                return true;
    
            // Handle NaN, Infinity.
            if (Double.IsInfinity(value1) | Double.IsNaN(value1))
                return value1.Equals(value2);
            else if (Double.IsInfinity(value2) | Double.IsNaN(value2))
                return value1.Equals(value2);
    
            // Handle zero to avoid division by zero
            double divisor = Math.Max(value1, value2);
            if (divisor.Equals(0))
                divisor = Math.Min(value1, value2);
    
            return Math.Abs((value1 - value2) / divisor) <= epsilon;
        }
    }
    // The example displays the following output:
    //       1 = 1.00000012: False
    //       1 is approximately equal to 1.00000012: True
    //       -1 is approximately equal to -1.00000012: True
    
    open System
    
    let isApproximatelyEqual value1 value2 epsilon =
        // If they are equal anyway, just return True.
        if value1.Equals value2 then 
            true
        // Handle NaN, Infinity.
        elif Single.IsInfinity value1 || Single.IsNaN value1 then
            value1.Equals value2
        elif Single.IsInfinity value2 || Single.IsNaN value2 then
            value1.Equals value2
        else
            // Handle zero to avoid division by zero
            let divisor = max value1 value2
            let divisor = 
                if divisor.Equals 0 then
                    min value1 value2
                else divisor
            abs (value1 - value2) / divisor <= epsilon           
    
    
    let one1 = 0.1f * 10f
    let mutable one2 = 0f
    for _ = 1 to 10 do
       one2 <- one2 + 0.1f
    
    printfn $"{one1:R} = {one2:R}: {one1.Equals one2}"
    printfn $"{one1:R} is approximately equal to {one2:R}: {isApproximatelyEqual one1 one2 0.000001f}" 
    // The example displays the following output:
    //       1 = 1.00000012: False
    //       1 is approximately equal to 1.00000012: True
    
    Module Example4
        Public Sub Main()
            Dim one1 As Single = 0.1 * 10
            Dim one2 As Single = 0
            For ctr As Integer = 1 To 10
                one2 += CSng(0.1)
            Next
            Console.WriteLine("{0:R} = {1:R}: {2}", one1, one2, one1.Equals(one2))
            Console.WriteLine("{0:R} is approximately equal to {1:R}: {2}",
                            one1, one2,
                            IsApproximatelyEqual(one1, one2, 0.000001))
        End Sub
    
        Function IsApproximatelyEqual(value1 As Single, value2 As Single,
                                     epsilon As Single) As Boolean
            ' If they are equal anyway, just return True.
            If value1.Equals(value2) Then Return True
    
            ' Handle NaN, Infinity.
            If Single.IsInfinity(value1) Or Single.IsNaN(value1) Then
                Return value1.Equals(value2)
            ElseIf Single.IsInfinity(value2) Or Single.IsNaN(value2) Then
                Return value1.Equals(value2)
            End If
    
            ' Handle zero to avoid division by zero
            Dim divisor As Single = Math.Max(value1, value2)
            If divisor.Equals(0) Then
                divisor = Math.Min(value1, value2)
            End If
    
            Return Math.Abs(value1 - value2) / divisor <= epsilon
        End Function
    End Module
    ' The example displays the following output:
    '       1 = 1.00000012: False
    '       1 is approximately equal to 1.00000012: True
    

Исключения и значения с плавающей запятой

Операции с числами с плавающей точкой не вызывают исключений, в отличие от операций с целочисленными типами, которые вызывают исключения в случаях незаконных операций, таких как деление на ноль или переполнение. Вместо этого в этих ситуациях результат операции с плавающей запятой равен нулю, положительной бесконечности, отрицательной бесконечности или не числом (NaN):

  • Если результат операции с плавающей запятой слишком мал для целевого формата, результат равен нулю. Это может произойти при умножении двух очень маленьких чисел с плавающей запятой, как показано в следующем примере.

    using System;
    
    public class Example6
    {
        public static void Main()
        {
            float value1 = 1.163287e-36f;
            float value2 = 9.164234e-25f;
            float result = value1 * value2;
            Console.WriteLine($"{value1} * {value2} = {result}");
            Console.WriteLine($"{result} = 0: {result.Equals(0.0f)}");
        }
    }
    // The example displays the following output:
    //       1.163287E-36 * 9.164234E-25 = 0
    //       0 = 0: True
    
    let value1 = 1.163287e-36f
    let value2 = 9.164234e-25f
    let result = value1 * value2
    printfn $"{value1} * {value2} = {result}"
    printfn $"{result} = 0: {result.Equals(0f)}"
    // The example displays the following output:
    //       1.163287E-36 * 9.164234E-25 = 0
    //       0 = 0: True
    
    Module Example7
        Public Sub Main()
            Dim value1 As Single = 1.163287E-36
            Dim value2 As Single = 9.164234E-25
            Dim result As Single = value1 * value2
            Console.WriteLine("{0} * {1} = {2:R}", value1, value2, result)
            Console.WriteLine("{0} = 0: {1}", result, result.Equals(0))
        End Sub
    End Module
    ' The example displays the following output:
    '       1.163287E-36 * 9.164234E-25 = 0
    '       0 = 0: True
    
  • Если величина результата операции с плавающей запятой превышает диапазон целевого формата, результат операции — PositiveInfinity или NegativeInfinity, как это подходит для знака результата. Результат операции, которая переполняет Single.MaxValue, это PositiveInfinity, а результат операции, которая переполняет Single.MinValue, это NegativeInfinity, как показано в следующем примере.

    using System;
    
    public class Example7
    {
        public static void Main()
        {
            float value1 = 3.065e35f;
            float value2 = 6.9375e32f;
            float result = value1 * value2;
            Console.WriteLine($"PositiveInfinity: {Single.IsPositiveInfinity(result)}");
            Console.WriteLine($"NegativeInfinity: {Single.IsNegativeInfinity(result)}");
            Console.WriteLine();
    
            value1 = -value1;
            result = value1 * value2;
            Console.WriteLine($"PositiveInfinity: {Single.IsPositiveInfinity(result)}");
            Console.WriteLine($"NegativeInfinity: {Single.IsNegativeInfinity(result)}");
        }
    }
    
    // The example displays the following output:
    //       PositiveInfinity: True
    //       NegativeInfinity: False
    //       
    //       PositiveInfinity: False
    //       NegativeInfinity: True
    
    open System
    
    let value1 = 3.065e35f
    let value2 = 6.9375e32f
    let result = value1 * value2
    printfn $"PositiveInfinity: {Single.IsPositiveInfinity result}" 
    printfn $"NegativeInfinity: {Single.IsNegativeInfinity result}\n"
    
    let value3 = -value1
    let result2 = value3 * value2
    printfn $"PositiveInfinity: {Single.IsPositiveInfinity result}" 
    printfn $"NegativeInfinity: {Single.IsNegativeInfinity result}" 
    
    // The example displays the following output:
    //       PositiveInfinity: True
    //       NegativeInfinity: False
    //       
    //       PositiveInfinity: False
    //       NegativeInfinity: True
    
    Module Example8
        Public Sub Main()
            Dim value1 As Single = 3.065E+35
            Dim value2 As Single = 6.9375E+32
            Dim result As Single = value1 * value2
            Console.WriteLine("PositiveInfinity: {0}",
                             Single.IsPositiveInfinity(result))
            Console.WriteLine("NegativeInfinity: {0}",
                            Single.IsNegativeInfinity(result))
            Console.WriteLine()
            value1 = -value1
            result = value1 * value2
            Console.WriteLine("PositiveInfinity: {0}",
                             Single.IsPositiveInfinity(result))
            Console.WriteLine("NegativeInfinity: {0}",
                            Single.IsNegativeInfinity(result))
        End Sub
    End Module
    ' The example displays the following output:
    '       PositiveInfinity: True
    '       NegativeInfinity: False
    '       
    '       PositiveInfinity: False
    '       NegativeInfinity: True
    

    PositiveInfinity также является результатом деления положительного числа на ноль, а NegativeInfinity является результатом деления отрицательного числа на ноль.

  • Если операция с плавающей запятой недопустима, результат операции NaN. Например, NaN - результат от следующих операций:

    • Деление на ноль с дивидендами от нуля. Обратите внимание, что другие случаи деления на ноль приводят либо к PositiveInfinity, либо к NegativeInfinity.
    • Любая операция с плавающей запятой с недопустимыми входными данными. Например, попытка найти квадратный корень отрицательного значения возвращает NaN.
    • Любая операция с аргументом, значение которого равно Single.NaN.

Преобразование типов

Структура Single не определяет никаких явных или неявных операторов преобразования; Вместо этого преобразования реализуются компилятором.

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

Преобразование из Расширение и сужение Возможная потеря точности
Byte Расширение Нет
Decimal Расширение

Обратите внимание, что для C# требуется оператор приведения.
Да. Decimal поддерживает 29 десятичных цифр точности; Single поддерживает 9.
Double Сужение; Значения вне диапазона преобразуются в Double.NegativeInfinity или Double.PositiveInfinity. Да. Double поддерживает 17 десятичных цифр точности; Single поддерживает 9.
Int16 Расширение Нет
Int32 Расширение Да. Int32 поддерживает 10 десятичных цифр точности; Single поддерживает 9.
Int64 Расширение Да. Int64 поддерживает 19 десятичных цифр точности; Single поддерживает 9.
SByte Расширение Нет
UInt16 Расширение Нет
UInt32 Расширение Да. UInt32 поддерживает 10 десятичных цифр точности; Single поддерживает 9.
UInt64 Расширение Да. Int64 поддерживает 20 десятичных цифр точности; Single поддерживает 9.

Следующий пример преобразует минимальное или максимальное значение других примитивных числовых типов в значение Single.

using System;

public class Example4
{
    public static void Main()
    {
        dynamic[] values = { Byte.MinValue, Byte.MaxValue, Decimal.MinValue,
                           Decimal.MaxValue, Double.MinValue, Double.MaxValue,
                           Int16.MinValue, Int16.MaxValue, Int32.MinValue,
                           Int32.MaxValue, Int64.MinValue, Int64.MaxValue,
                           SByte.MinValue, SByte.MaxValue, UInt16.MinValue,
                           UInt16.MaxValue, UInt32.MinValue, UInt32.MaxValue,
                           UInt64.MinValue, UInt64.MaxValue };
        float sngValue;
        foreach (var value in values)
        {
            if (value.GetType() == typeof(Decimal) ||
                value.GetType() == typeof(Double))
                sngValue = (float)value;
            else
                sngValue = value;
            Console.WriteLine($"{value} ({value.GetType().Name}) --> {sngValue:R} ({sngValue.GetType().Name})");
        }
    }
}
// The example displays the following output:
//       0 (Byte) --> 0 (Single)
//       255 (Byte) --> 255 (Single)
//       -79228162514264337593543950335 (Decimal) --> -7.92281625E+28 (Single)
//       79228162514264337593543950335 (Decimal) --> 7.92281625E+28 (Single)
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//       -32768 (Int16) --> -32768 (Single)
//       32767 (Int16) --> 32767 (Single)
//       -2147483648 (Int32) --> -2.14748365E+09 (Single)
//       2147483647 (Int32) --> 2.14748365E+09 (Single)
//       -9223372036854775808 (Int64) --> -9.223372E+18 (Single)
//       9223372036854775807 (Int64) --> 9.223372E+18 (Single)
//       -128 (SByte) --> -128 (Single)
//       127 (SByte) --> 127 (Single)
//       0 (UInt16) --> 0 (Single)
//       65535 (UInt16) --> 65535 (Single)
//       0 (UInt32) --> 0 (Single)
//       4294967295 (UInt32) --> 4.2949673E+09 (Single)
//       0 (UInt64) --> 0 (Single)
//       18446744073709551615 (UInt64) --> 1.84467441E+19 (Single)
open System

let values: obj list = 
    [ Byte.MinValue; Byte.MaxValue; Decimal.MinValue
      Decimal.MaxValue; Double.MinValue; Double.MaxValue
      Int16.MinValue; Int16.MaxValue; Int32.MinValue
      Int32.MaxValue; Int64.MinValue; Int64.MaxValue
      SByte.MinValue; SByte.MaxValue; UInt16.MinValue
      UInt16.MaxValue; UInt32.MinValue; UInt32.MaxValue
      UInt64.MinValue; UInt64.MaxValue ]

for value in values do
    let sngValue = 
        match value with
        | :? byte as v -> float32 v
        | :? decimal as v -> float32 v
        | :? double as v -> float32 v
        | :? int16 as v -> float32 v
        | :? int as v -> float32 v
        | :? int64 as v -> float32 v
        | :? int8 as v -> float32 v
        | :? uint16 as v -> float32 v
        | :? uint as v -> float32 v
        | :? uint64 as v -> float32 v
        | _ -> raise (NotImplementedException "Unknown Type")
    printfn $"{value} ({value.GetType().Name}) --> {sngValue:R} ({sngValue.GetType().Name})"
// The example displays the following output:
//       0 (Byte) --> 0 (Single)
//       255 (Byte) --> 255 (Single)
//       -79228162514264337593543950335 (Decimal) --> -7.92281625E+28 (Single)
//       79228162514264337593543950335 (Decimal) --> 7.92281625E+28 (Single)
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//       -32768 (Int16) --> -32768 (Single)
//       32767 (Int16) --> 32767 (Single)
//       -2147483648 (Int32) --> -2.14748365E+09 (Single)
//       2147483647 (Int32) --> 2.14748365E+09 (Single)
//       -9223372036854775808 (Int64) --> -9.223372E+18 (Single)
//       9223372036854775807 (Int64) --> 9.223372E+18 (Single)
//       -128 (SByte) --> -128 (Single)
//       127 (SByte) --> 127 (Single)
//       0 (UInt16) --> 0 (Single)
//       65535 (UInt16) --> 65535 (Single)
//       0 (UInt32) --> 0 (Single)
//       4294967295 (UInt32) --> 4.2949673E+09 (Single)
//       0 (UInt64) --> 0 (Single)
//       18446744073709551615 (UInt64) --> 1.84467441E+19 (Single)
Module Example5
    Public Sub Main()
        Dim values() As Object = {Byte.MinValue, Byte.MaxValue, Decimal.MinValue,
                                 Decimal.MaxValue, Double.MinValue, Double.MaxValue,
                                 Int16.MinValue, Int16.MaxValue, Int32.MinValue,
                                 Int32.MaxValue, Int64.MinValue, Int64.MaxValue,
                                 SByte.MinValue, SByte.MaxValue, UInt16.MinValue,
                                 UInt16.MaxValue, UInt32.MinValue, UInt32.MaxValue,
                                 UInt64.MinValue, UInt64.MaxValue}
        Dim sngValue As Single
        For Each value In values
            If value.GetType() = GetType(Double) Then
                sngValue = CSng(value)
            Else
                sngValue = value
            End If
            Console.WriteLine("{0} ({1}) --> {2:R} ({3})",
                           value, value.GetType().Name,
                           sngValue, sngValue.GetType().Name)
        Next
    End Sub
End Module
' The example displays the following output:
'       0 (Byte) --> 0 (Single)
'       255 (Byte) --> 255 (Single)
'       -79228162514264337593543950335 (Decimal) --> -7.92281625E+28 (Single)
'       79228162514264337593543950335 (Decimal) --> 7.92281625E+28 (Single)
'       -1.79769313486232E+308 (Double) --> -Infinity (Single)
'       1.79769313486232E+308 (Double) --> Infinity (Single)
'       -32768 (Int16) --> -32768 (Single)
'       32767 (Int16) --> 32767 (Single)
'       -2147483648 (Int32) --> -2.14748365E+09 (Single)
'       2147483647 (Int32) --> 2.14748365E+09 (Single)
'       -9223372036854775808 (Int64) --> -9.223372E+18 (Single)
'       9223372036854775807 (Int64) --> 9.223372E+18 (Single)
'       -128 (SByte) --> -128 (Single)
'       127 (SByte) --> 127 (Single)
'       0 (UInt16) --> 0 (Single)
'       65535 (UInt16) --> 65535 (Single)
'       0 (UInt32) --> 0 (Single)
'       4294967295 (UInt32) --> 4.2949673E+09 (Single)
'       0 (UInt64) --> 0 (Single)
'       18446744073709551615 (UInt64) --> 1.84467441E+19 (Single)

Кроме того, значения DoubleDouble.NaN, Double.PositiveInfinityи Double.NegativeInfinity преобразуются в Single.NaN, Single.PositiveInfinityи Single.NegativeInfinityсоответственно.

Обратите внимание, что преобразование значения некоторых числовых типов в Single значение может привести к потере точности. Как показано в примере, при преобразовании Decimal, Double, Int32, Int64, UInt32и UInt64 значений в Single возможны потери точности.

Преобразование значения Single в Double является расширением преобразования. Преобразование может привести к потере точности, если тип Double не имеет точного представления для значения Single.

Преобразование значения Single в значение любого примитивного числового типа данных, отличного от Double, является сужающим преобразованием и требует оператора приведения (в C#) или метода преобразования (в Visual Basic). Значения, которые находятся за пределами диапазона целевого типа данных, определяемого MinValue и MaxValue свойств целевого типа, ведут себя, как показано в следующей таблице.

Тип объекта Результат
Любой целочисленный тип Исключение OverflowException, если преобразование происходит в проверенном контексте.

Если преобразование происходит в неконтролируемом контексте (по умолчанию в C#), преобразование выполняется успешно, но значение переполняется.
Decimal Исключение OverflowException

Кроме того, Single.NaN, Single.PositiveInfinityи Single.NegativeInfinity выбрасывают OverflowException для преобразования в целые числа в контексте с проверкой, но эти значения переполняются при преобразовании в целые числа в контексте без проверки. Для преобразования в Decimal они всегда выбрасывают OverflowException. Для преобразований в Doubleони преобразуются в Double.NaN, Double.PositiveInfinityи Double.NegativeInfinityсоответственно.

Обратите внимание, что потеря точности может привести к преобразованию значения Single в другой числовой тип. В случае преобразования нецелых значений Single, как показано в примере, дробный компонент теряется, когда значение Single округляется (как в Visual Basic) или усекается (как в C# и F#). Для преобразования в значения Decimal значение Single может не иметь точного представления в целевом типе данных.

В следующем примере число значений Single преобразуется в несколько других числовых типов. Преобразования происходят в проверяемом контексте в Visual Basic (по умолчанию), в C# (из-за проверенного ключевого слова) и в F# (из-за инструкции open Checked). Выходные данные из примера показывают результат преобразования как в проверенном, так и в непроверенном контексте. Вы можете выполнять преобразования в незаверяемом контексте в Visual Basic, скомпилируя с помощью переключателя компилятора /removeintchecks+, в C# закомментируя оператор checked и в F#, закомментируя оператор open Checked.

using System;

public class Example5
{
    public static void Main()
    {
        float[] values = { Single.MinValue, -67890.1234f, -12345.6789f,
                         12345.6789f, 67890.1234f, Single.MaxValue,
                         Single.NaN, Single.PositiveInfinity,
                         Single.NegativeInfinity };
        checked
        {
            foreach (var value in values)
            {
                try
                {
                    Int64 lValue = (long)value;
                    Console.WriteLine($"{value} ({value.GetType().Name}) --> {lValue} (0x{lValue:X16}) ({lValue.GetType().Name})");
                }
                catch (OverflowException)
                {
                    Console.WriteLine($"Unable to convert {value} to Int64.");
                }
                try
                {
                    UInt64 ulValue = (ulong)value;
                    Console.WriteLine($"{value} ({value.GetType().Name}) --> {ulValue} (0x{ulValue:X16}) ({ulValue.GetType().Name})");
                }
                catch (OverflowException)
                {
                    Console.WriteLine($"Unable to convert {value} to UInt64.");
                }
                try
                {
                    Decimal dValue = (decimal)value;
                    Console.WriteLine($"{value} ({value.GetType().Name}) --> {dValue} ({dValue.GetType().Name})");
                }
                catch (OverflowException)
                {
                    Console.WriteLine($"Unable to convert {value} to Decimal.");
                }

                Double dblValue = value;
                Console.WriteLine($"{value} ({value.GetType().Name}) --> {dblValue} ({dblValue.GetType().Name})");
                Console.WriteLine();
            }
        }
    }
}
// The example displays the following output for conversions performed
// in a checked context:
//       Unable to convert -3.402823E+38 to Int64.
//       Unable to convert -3.402823E+38 to UInt64.
//       Unable to convert -3.402823E+38 to Decimal.
//       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
//
//       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       Unable to convert -67890.13 to UInt64.
//       -67890.13 (Single) --> -67890.12 (Decimal)
//       -67890.13 (Single) --> -67890.125 (Double)
//
//       -12345.68 (Single) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       Unable to convert -12345.68 to UInt64.
//       -12345.68 (Single) --> -12345.68 (Decimal)
//       -12345.68 (Single) --> -12345.6787109375 (Double)
//
//       12345.68 (Single) --> 12345 (0x0000000000003039) (Int64)
//       12345.68 (Single) --> 12345 (0x0000000000003039) (UInt64)
//       12345.68 (Single) --> 12345.68 (Decimal)
//       12345.68 (Single) --> 12345.6787109375 (Double)
//
//       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
//       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
//       67890.13 (Single) --> 67890.12 (Decimal)
//       67890.13 (Single) --> 67890.125 (Double)
//
//       Unable to convert 3.402823E+38 to Int64.
//       Unable to convert 3.402823E+38 to UInt64.
//       Unable to convert 3.402823E+38 to Decimal.
//       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
//
//       Unable to convert NaN to Int64.
//       Unable to convert NaN to UInt64.
//       Unable to convert NaN to Decimal.
//       NaN (Single) --> NaN (Double)
//
//       Unable to convert Infinity to Int64.
//       Unable to convert Infinity to UInt64.
//       Unable to convert Infinity to Decimal.
//       Infinity (Single) --> Infinity (Double)
//
//       Unable to convert -Infinity to Int64.
//       Unable to convert -Infinity to UInt64.
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Single) --> -Infinity (Double)
// The example displays the following output for conversions performed
// in an unchecked context:
//       -3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -3.402823E+38 (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -3.402823E+38 to Decimal.
//       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
//
//       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       -67890.13 (Single) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
//       -67890.13 (Single) --> -67890.12 (Decimal)
//       -67890.13 (Single) --> -67890.125 (Double)
//
//       -12345.68 (Single) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       -12345.68 (Single) --> 18446744073709539271 (0xFFFFFFFFFFFFCFC7) (UInt64)
//       -12345.68 (Single) --> -12345.68 (Decimal)
//       -12345.68 (Single) --> -12345.6787109375 (Double)
//
//       12345.68 (Single) --> 12345 (0x0000000000003039) (Int64)
//       12345.68 (Single) --> 12345 (0x0000000000003039) (UInt64)
//       12345.68 (Single) --> 12345.68 (Decimal)
//       12345.68 (Single) --> 12345.6787109375 (Double)
//
//       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
//       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
//       67890.13 (Single) --> 67890.12 (Decimal)
//       67890.13 (Single) --> 67890.125 (Double)
//
//       3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       3.402823E+38 (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert 3.402823E+38 to Decimal.
//       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
//
//       NaN (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       NaN (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert NaN to Decimal.
//       NaN (Single) --> NaN (Double)
//
//       Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       Infinity (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert Infinity to Decimal.
//       Infinity (Single) --> Infinity (Double)
//
//       -Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -Infinity (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Single) --> -Infinity (Double)
open System
open Checked

let values = 
    [ Single.MinValue; -67890.1234f; -12345.6789f
      12345.6789f; 67890.1234f; Single.MaxValue
      Single.NaN; Single.PositiveInfinity
      Single.NegativeInfinity ]

for value in values do
    try
        let lValue = int64 value
        printfn $"{value} ({value.GetType().Name}) --> {lValue} (0x{lValue:X16}) ({lValue.GetType().Name})"
    with :? OverflowException ->
        printfn $"Unable to convert {value} to Int64."
    try
        let ulValue = uint64 value
        printfn $"{value} ({value.GetType().Name}) --> {ulValue} (0x{ulValue:X16}) ({ulValue.GetType().Name})"
    with :? OverflowException ->
        printfn $"Unable to convert {value} to UInt64."
    try
        let dValue = decimal value
        printfn $"{value} ({value.GetType().Name}) --> {dValue} ({dValue.GetType().Name})"
    with :? OverflowException ->
        printfn $"Unable to convert {value} to Decimal."

    let dblValue = double value
    printfn $"{value} ({value.GetType().Name}) --> {dblValue} ({dblValue.GetType().Name})\n"
// The example displays the following output for conversions performed
// in a checked context:
//       Unable to convert -3.402823E+38 to Int64.
//       Unable to convert -3.402823E+38 to UInt64.
//       Unable to convert -3.402823E+38 to Decimal.
//       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
//
//       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       Unable to convert -67890.13 to UInt64.
//       -67890.13 (Single) --> -67890.12 (Decimal)
//       -67890.13 (Single) --> -67890.125 (Double)
//
//       -12345.68 (Single) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       Unable to convert -12345.68 to UInt64.
//       -12345.68 (Single) --> -12345.68 (Decimal)
//       -12345.68 (Single) --> -12345.6787109375 (Double)
//
//       12345.68 (Single) --> 12345 (0x0000000000003039) (Int64)
//       12345.68 (Single) --> 12345 (0x0000000000003039) (UInt64)
//       12345.68 (Single) --> 12345.68 (Decimal)
//       12345.68 (Single) --> 12345.6787109375 (Double)
//
//       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
//       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
//       67890.13 (Single) --> 67890.12 (Decimal)
//       67890.13 (Single) --> 67890.125 (Double)
//
//       Unable to convert 3.402823E+38 to Int64.
//       Unable to convert 3.402823E+38 to UInt64.
//       Unable to convert 3.402823E+38 to Decimal.
//       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
//
//       Unable to convert NaN to Int64.
//       Unable to convert NaN to UInt64.
//       Unable to convert NaN to Decimal.
//       NaN (Single) --> NaN (Double)
//
//       Unable to convert Infinity to Int64.
//       Unable to convert Infinity to UInt64.
//       Unable to convert Infinity to Decimal.
//       Infinity (Single) --> Infinity (Double)
//
//       Unable to convert -Infinity to Int64.
//       Unable to convert -Infinity to UInt64.
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Single) --> -Infinity (Double)
// The example displays the following output for conversions performed
// in an unchecked context:
//       -3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -3.402823E+38 (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -3.402823E+38 to Decimal.
//       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
//
//       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       -67890.13 (Single) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
//       -67890.13 (Single) --> -67890.12 (Decimal)
//       -67890.13 (Single) --> -67890.125 (Double)
//
//       -12345.68 (Single) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       -12345.68 (Single) --> 18446744073709539271 (0xFFFFFFFFFFFFCFC7) (UInt64)
//       -12345.68 (Single) --> -12345.68 (Decimal)
//       -12345.68 (Single) --> -12345.6787109375 (Double)
//
//       12345.68 (Single) --> 12345 (0x0000000000003039) (Int64)
//       12345.68 (Single) --> 12345 (0x0000000000003039) (UInt64)
//       12345.68 (Single) --> 12345.68 (Decimal)
//       12345.68 (Single) --> 12345.6787109375 (Double)
//
//       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
//       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
//       67890.13 (Single) --> 67890.12 (Decimal)
//       67890.13 (Single) --> 67890.125 (Double)
//
//       3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       3.402823E+38 (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert 3.402823E+38 to Decimal.
//       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
//
//       NaN (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       NaN (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert NaN to Decimal.
//       NaN (Single) --> NaN (Double)
//
//       Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       Infinity (Single) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert Infinity to Decimal.
//       Infinity (Single) --> Infinity (Double)
//
//       -Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -Infinity (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Single) --> -Infinity (Double)
Module Example6
    Public Sub Main()
        Dim values() As Single = {Single.MinValue, -67890.1234, -12345.6789,
                                 12345.6789, 67890.1234, Single.MaxValue,
                                 Single.NaN, Single.PositiveInfinity,
                                 Single.NegativeInfinity}
        For Each value In values
            Try
                Dim lValue As Long = CLng(value)
                Console.WriteLine("{0} ({1}) --> {2} (0x{2:X16}) ({3})",
                               value, value.GetType().Name,
                               lValue, lValue.GetType().Name)
            Catch e As OverflowException
                Console.WriteLine("Unable to convert {0} to Int64.", value)
            End Try
            Try
                Dim ulValue As UInt64 = CULng(value)
                Console.WriteLine("{0} ({1}) --> {2} (0x{2:X16}) ({3})",
                               value, value.GetType().Name,
                               ulValue, ulValue.GetType().Name)
            Catch e As OverflowException
                Console.WriteLine("Unable to convert {0} to UInt64.", value)
            End Try
            Try
                Dim dValue As Decimal = CDec(value)
                Console.WriteLine("{0} ({1}) --> {2} ({3})",
                               value, value.GetType().Name,
                               dValue, dValue.GetType().Name)
            Catch e As OverflowException
                Console.WriteLine("Unable to convert {0} to Decimal.", value)
            End Try

            Dim dblValue As Double = value
            Console.WriteLine("{0} ({1}) --> {2} ({3})",
                           value, value.GetType().Name,
                           dblValue, dblValue.GetType().Name)
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output for conversions performed
' in a checked context:
'       Unable to convert -3.402823E+38 to Int64.
'       Unable to convert -3.402823E+38 to UInt64.
'       Unable to convert -3.402823E+38 to Decimal.
'       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
'
'       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
'       Unable to convert -67890.13 to UInt64.
'       -67890.13 (Single) --> -67890.12 (Decimal)
'       -67890.13 (Single) --> -67890.125 (Double)
'
'       -12345.68 (Single) --> -12346 (0xFFFFFFFFFFFFCFC6) (Int64)
'       Unable to convert -12345.68 to UInt64.
'       -12345.68 (Single) --> -12345.68 (Decimal)
'       -12345.68 (Single) --> -12345.6787109375 (Double)
'
'       12345.68 (Single) --> 12346 (0x000000000000303A) (Int64)
'       12345.68 (Single) --> 12346 (0x000000000000303A) (UInt64)
'       12345.68 (Single) --> 12345.68 (Decimal)
'       12345.68 (Single) --> 12345.6787109375 (Double)
'
'       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
'       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
'       67890.13 (Single) --> 67890.12 (Decimal)
'       67890.13 (Single) --> 67890.125 (Double)
'
'       Unable to convert 3.402823E+38 to Int64.
'       Unable to convert 3.402823E+38 to UInt64.
'       Unable to convert 3.402823E+38 to Decimal.
'       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
'
'       Unable to convert NaN to Int64.
'       Unable to convert NaN to UInt64.
'       Unable to convert NaN to Decimal.
'       NaN (Single) --> NaN (Double)
'
'       Unable to convert Infinity to Int64.
'       Unable to convert Infinity to UInt64.
'       Unable to convert Infinity to Decimal.
'       Infinity (Single) --> Infinity (Double)
'
'       Unable to convert -Infinity to Int64.
'       Unable to convert -Infinity to UInt64.
'       Unable to convert -Infinity to Decimal.
'       -Infinity (Single) --> -Infinity (Double)
' The example displays the following output for conversions performed
' in an unchecked context:
'       -3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       -3.402823E+38 (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
'       Unable to convert -3.402823E+38 to Decimal.
'       -3.402823E+38 (Single) --> -3.40282346638529E+38 (Double)
'
'       -67890.13 (Single) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
'       -67890.13 (Single) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
'       -67890.13 (Single) --> -67890.12 (Decimal)
'       -67890.13 (Single) --> -67890.125 (Double)
'
'       -12345.68 (Single) --> -12346 (0xFFFFFFFFFFFFCFC6) (Int64)
'       -12345.68 (Single) --> 18446744073709539270 (0xFFFFFFFFFFFFCFC6) (UInt64)
'       -12345.68 (Single) --> -12345.68 (Decimal)
'       -12345.68 (Single) --> -12345.6787109375 (Double)
'
'       12345.68 (Single) --> 12346 (0x000000000000303A) (Int64)
'       12345.68 (Single) --> 12346 (0x000000000000303A) (UInt64)
'       12345.68 (Single) --> 12345.68 (Decimal)
'       12345.68 (Single) --> 12345.6787109375 (Double)
'
'       67890.13 (Single) --> 67890 (0x0000000000010932) (Int64)
'       67890.13 (Single) --> 67890 (0x0000000000010932) (UInt64)
'       67890.13 (Single) --> 67890.12 (Decimal)
'       67890.13 (Single) --> 67890.125 (Double)
'
'       3.402823E+38 (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       3.402823E+38 (Single) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert 3.402823E+38 to Decimal.
'       3.402823E+38 (Single) --> 3.40282346638529E+38 (Double)
'
'       NaN (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       NaN (Single) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert NaN to Decimal.
'       NaN (Single) --> NaN (Double)
'
'       Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       Infinity (Single) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert Infinity to Decimal.
'       Infinity (Single) --> Infinity (Double)
'
'       -Infinity (Single) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       -Infinity (Single) --> 9223372036854775808 (0x8000000000000000) (UInt64)
'       Unable to convert -Infinity to Decimal.
'       -Infinity (Single) --> -Infinity (Double)

Дополнительные сведения о преобразовании числовых типов см. в разделе Преобразование типов в .NET и таблицы преобразования типов .

Функциональность для чисел с плавающей запятой

Структура Single и связанные типы предоставляют методы для выполнения следующих категорий операций:

  • сравнение значений. Можно вызвать метод Equals, чтобы определить, равны ли два значения Single или метод CompareTo для определения связи между двумя значениями.

    Структура Single также поддерживает полный набор операторов сравнения. Например, можно проверить равенство или неравенство или определить, больше ли одно значение или равно другому значению. Если один из операндов является Double, то значение Single преобразуется в Double перед выполнением сравнения. Если один из операндов является целочисленным типом, он преобразуется в Single перед выполнением сравнения. Хотя эти преобразования расширяются, они могут привести к потере точности.

    Предупреждение

    Из-за различий в точности два значения Single, которые, как ожидается, будут равными, могут оказаться неравными, что влияет на результат сравнения. Дополнительные сведения о сравнении двух значений Single".

    Можно также вызвать методы IsNaN, IsInfinity, IsPositiveInfinityи IsNegativeInfinity для проверки этих специальных значений.

  • Математическая операция. Распространенные арифметические операции, такие как добавление, вычитание, умножение и деление, реализуются компиляторами языка и инструкциями CIL, а не методами Single. Если другой операнд в математической операции является Double, Single преобразуется в Double перед выполнением операции, а результат операции также является значением Double. Если другой операнд является целочисленным типом, он преобразуется в Single перед выполнением операции, а результат операции также является Single значением.

    Вы можете выполнять другие математические операции, вызывая методы static (Shared в Visual Basic) в классе System.Math. К ним относятся дополнительные методы, часто используемые для арифметики (например, Math.Abs, Math.Signи Math.Sqrt), геометрии (например, Math.Cos и Math.Sin), а также вычислений (например, Math.Log). Во всех случаях значение Single преобразуется в Double.

    Вы также можете управлять отдельными битами в значении Single. Метод BitConverter.GetBytes(Single) возвращает свой битовый шаблон в массиве байтов. Передав этот массив байтов в метод BitConverter.ToInt32, можно также сохранить битовое значение Single в 32-разрядном целочисленном значении.

  • округление. Округление часто используется в качестве метода для снижения влияния различий между значениями, вызванных проблемами представления чисел с плавающей запятой и их точности. Можно округить значение Single путем вызова метода Math.Round. Однако обратите внимание, что значение Single преобразуется в Double перед вызовом метода, а преобразование может привести к потере точности.

  • форматирование. Значение Single можно преобразовать в строковое представление, вызвав метод ToString или используя функцию составного форматирования . Сведения о том, как строки форматирования управляют строковым представлением значений с плавающей запятой, см. в темах Строки стандартного числового формата и Строки пользовательского числового формата.

  • Строки синтаксического анализа. Строковое представление значения с плавающей запятой можно преобразовать в значение Single путем вызова метода Parse или TryParse. Если операция синтаксического анализа завершается ошибкой, метод Parse создает исключение, а метод TryParse возвращает false.

  • Преобразование типов. Структура Single предоставляет явную реализацию интерфейса для интерфейса IConvertible, который поддерживает преобразование между двумя стандартными типами данных .NET. Компиляторы языка также поддерживают неявное преобразование значений для всех других стандартных числовых типов, за исключением преобразования Double в Single значений. Преобразование значения любого стандартного числового типа, отличного от Double в Single, является расширяющим преобразованием и не требует использования оператора приведения или метода преобразования.

    Однако преобразование 32-разрядных и 64-разрядных целых значений может привести к потере точности. В следующей таблице перечислены различия в точности для 32-разрядных, 64-разрядных и Double типов:

    Тип Максимальная точность (в десятичных цифрах) Внутренняя точность (в десятичных цифрах)
    Double 15 17
    Int32 и UInt32. 10 10
    Int64 и UInt64. 19 19
    Single 7 9

    Проблема точности чаще всего влияет на значения Single, которые преобразуются в значения Double. В следующем примере два значения, созданные идентичными операциями деления, неравны, поскольку одно из значений — это значение с плавающей запятой с одной точностью, которое преобразуется в Double.

    using System;
    
    public class Example8
    {
        public static void Main()
        {
            Double value1 = 1 / 3.0;
            Single sValue2 = 1 / 3.0f;
            Double value2 = (Double)sValue2;
            Console.WriteLine($"{value1:R} = {value2:R}: {value1.Equals(value2)}");
        }
    }
    // The example displays the following output:
    //        0.33333333333333331 = 0.3333333432674408: False
    
    let value1 = 1. / 3.
    let sValue2 = 1f / 3f
    let value2 = double sValue2
    printfn $"{value1:R} = {value2:R}: {value1.Equals value2}"
    // The example displays the following output:
    //        0.33333333333333331 = 0.3333333432674408: False
    
    Module Example9
        Public Sub Main()
            Dim value1 As Double = 1 / 3
            Dim sValue2 As Single = 1 / 3
            Dim value2 As Double = CDbl(sValue2)
            Console.WriteLine("{0} = {1}: {2}", value1, value2, value1.Equals(value2))
        End Sub
    End Module
    ' The example displays the following output:
    '       0.33333333333333331 = 0.3333333432674408: False