次の方法で共有


System.Double 構造体

この記事では、この API のリファレンス ドキュメントに補足的な解説を提供します。

Double 値型は、負の 1.79769313486232e308 から正の 1.79769313486232e308 までの値、正または負のゼロ、PositiveInfinityNegativeInfinity、非数 (NaN) を表す 64 ビットの倍精度数です。 これは、非常に大きな値 (惑星間や銀河間の距離など) や非常に小さな値 (キログラム単位の物質の分子質量など)、そして多くの場合、不正確になる値 (地球から別の恒星系までの距離など) を表現することを目的としています。 Double 型は、2 進浮動小数点演算に関する IEC 60559:1989 (IEEE 754) 標準に準拠しています。

浮動小数点の表現と精度

Double データ型は、次の表に示すように、倍精度浮動小数点値を 64 ビットの 2 進形式で格納します。

パーツ ビット
仮数 (Significand/mantissa) 0-51
指数 52-62
符号 (0 = 正、1 = 負) 63

10 進小数が一部の小数値 (1/3 や Math.PI など) を正確に表現できないのと同様に、2 進小数も一部の小数値を正確に表現できません。 たとえば、1/10 は 10 進小数では .1 で正確に表現されますが、2 進小数では .001100110011... となり、「0011」のパターンが無限に繰り返されます。 この場合、浮動小数点値は表現しようとしている数値を不正確に表しています。 元の浮動小数点値に対してさらに算術演算を行うと、精度損失が増す傾向があります。 たとえば、0.1 に 10 を掛けた結果と、0.1 を 9 回足した結果を比較すると、足した方が 8 回多く演算を実行しているため、より精度の低い結果が生成されていることがわかります。 この不一致は、2 つの Double 値を「R」標準数値書式指定文字列を使用して表示した場合にのみわかります。この書式指定文字列は、必要に応じて Double 型がサポートする 17 桁すべての精度を表示します。

using System;

public class Example13
{
    public static void Main()
    {
        Double value = .1;
        Double result1 = value * 10;
        Double result2 = 0;
        for (int ctr = 1; ctr <= 10; ctr++)
            result2 += value;

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

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

一部の数値は 2 進小数では正確に表現できないため、浮動小数点数は実数を近似することしかできません。

すべての浮動小数点数には有効桁数の制限もあり、これも浮動小数点値が実数をどれだけ正確に近似できるかを左右します。 Double 値は最大 15 桁の 10 進精度を持ちますが、内部的には最大 17 桁が保持されています。 つまり、一部の浮動小数点演算では、浮動小数点値を変更するのに十分な精度がない場合があります。 具体的な例を次に示します。 ここでは、非常に大きな浮動小数点値を定義し、それに Double.Epsilon と 1,000 兆 (10 の15 乗) の積を足しています。 しかし、その積は元の浮動小数点値を変更するには小さすぎます。 その最下位桁は 10 の -3 乗 (1,000 分の 1) ですが、積の最上位桁は 10 の -309 乗です。

using System;

public class Example14
{
    public static void Main()
    {
        Double value = 123456789012.34567;
        Double additional = Double.Epsilon * 1e15;
        Console.WriteLine($"{value} + {additional} = {value + additional}");
    }
}
// The example displays the following output:
//    123456789012.346 + 4.94065645841247E-309 = 123456789012.346
open System

let value = 123456789012.34567
let additional = Double.Epsilon * 1e15
printfn $"{value} + {additional} = {value + additional}"
// The example displays the following output:
//    123456789012.346 + 4.94065645841247E-309 = 123456789012.346
Module Example15
    Public Sub Main()
        Dim value As Double = 123456789012.34567
        Dim additional As Double = Double.Epsilon * 1.0E+15
        Console.WriteLine("{0} + {1} = {2}", value, additional,
                                           value + additional)
    End Sub
End Module
' The example displays the following output:
'   123456789012.346 + 4.94065645841247E-309 = 123456789012.346

浮動小数点数の有効桁数により、複数の結果が得られます。

  • 特定の有効桁数で等しく見える 2 つの浮動小数点数が、最小有効数字が異なっているために等しくない場合があります。 次の例では、一連の数値を合計し、その合計値を想定値と比較しています。 2 つの値は同じに見えますが、Equals メソッドを呼び出すと、それらが等しくないことが示されます。

    using System;
    
    public class Example10
    {
        public static void Main()
        {
            Double[] values = { 10.0, 2.88, 2.88, 2.88, 9.0 };
            Double result = 27.64;
            Double total = 0;
            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 (36.64) does not equal the total (36.64).
    //
    // 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).
    
    let values = [ 10.0; 2.88; 2.88; 2.88; 9.0 ]
    let result = 27.64
    let total = List.sum values
    
    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 (36.64) does not equal the total (36.64).
    //
    // 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).
    
    Module Example11
        Public Sub Main()
            Dim values() As Double = {10.0, 2.88, 2.88, 2.88, 9.0}
            Dim result As Double = 27.64
            Dim total As Double
            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 (36.64) does not equal the total (36.64).   
    '
    ' 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} に変更して 2 つの Double 値のすべての有効桁数を表示すると、加算演算中の精度損失により 2 つの値が等しくないことがわかります。 この場合、比較を実行する前に Math.Round(Double, Int32) メソッドを呼び出して、Double 値を目的の精度に丸めることで問題を解決できます。

  • 浮動小数点数を使用する算術演算または比較演算で小数が使用されると、2 進浮動小数点数がその小数に等しくない場合があるため、同じ結果が得られないことがあります。 前の例では、0.1 に 10 を掛けた結果と 0.1 を複数回足した結果を表示することで、この点を示しました。

    小数値を含む数値演算の精度が重要な場合は、Decimal 型ではなく Double 型を使用できます。 Int128UInt128 型の範囲から外れる整数演算で精度が重要な場合は、BigInteger 型を使用してください。

  • 浮動小数点数が含まれていると、値がラウンド トリップ されない場合があります。 値のラウンド トリップとは、演算で元の浮動小数点数が別の形式に変換され、逆の演算で変換後の形式から浮動小数点数に戻されて、最終の浮動小数点数が元の浮動小数点数に等しくなる場合をいいます。 変換で最下位桁が 1 桁以上失われるか、または変更された場合は、ラウンド トリップが失敗します。

    次の例では、3 つの Double 値を文字列に変換し、ファイルに保存しています。 この例を .NET Framework で実行すると、値は同じに見えても、復元された値は元の値と等しくありません。 (この問題はその後 .NET で解決されており、値は正しくラウンド トリップされます)

    StreamWriter sw = new(@"./Doubles.dat");
    double[] values = [2.2 / 1.01, 1.0 / 3, Math.PI];
    for (int ctr = 0; ctr < values.Length; ctr++)
    {
        sw.Write(values[ctr].ToString());
        if (ctr != values.Length - 1)
            sw.Write("|");
    }
    sw.Close();
    
    double[] restoredValues = new double[values.Length];
    StreamReader sr = new(@"./Doubles.dat");
    string temp = sr.ReadToEnd();
    string[] tempStrings = temp.Split('|');
    for (int ctr = 0; ctr < tempStrings.Length; ctr++)
        restoredValues[ctr] = double.Parse(tempStrings[ctr]);
    
    for (int ctr = 0; ctr < values.Length; ctr++)
    Console.WriteLine($"{values[ctr]} {(values[ctr].Equals(restoredValues[ctr]) ? "=" : "<>")} {restoredValues[ctr]}");
    
    // For .NET Framework only, the example displays the following output:
    //       2.17821782178218 <> 2.17821782178218
    //       0.333333333333333 <> 0.333333333333333
    //       3.14159265358979 <> 3.14159265358979
    
    open System
    open System.IO
    
    let values = [ 2.2 / 1.01; 1. / 3.; Math.PI ]
    
    using (new StreamWriter(@".\Doubles.dat")) (fun sw ->
        for i = 0 to values.Length - 1 do
            sw.Write(string values[i])
            if i <> values.Length - 1 then
                sw.Write "|")
    
    using (new StreamReader(@".\Doubles.dat")) (fun sr ->
        let temp = sr.ReadToEnd()
        let tempStrings = temp.Split '|'
    
        let restoredValues =
            [ for i = 0 to tempStrings.Length - 1 do
                  Double.Parse tempStrings[i] ]
    
        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.17821782178218 <> 2.17821782178218
    //       0.333333333333333 <> 0.333333333333333
    //       3.14159265358979 <> 3.14159265358979
    
    Imports System.IO
    
    Module Example12
        Public Sub Main()
            Dim sw As New StreamWriter(".\Doubles.dat")
            Dim values() As Double = {2.2 / 1.01, 1.0 / 3, 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 Double
            Dim sr As New StreamReader(".\Doubles.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) = Double.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.17821782178218 <> 2.17821782178218
    '       0.333333333333333 <> 0.333333333333333
    '       3.14159265358979 <> 3.14159265358979
    

    .NET Framework を対象としている場合は、「G17」標準数値書式指定文字列を使用して Double 値の完全な精度を保持することで、値を正しくラウンド トリップできます。

  • Single 値は Double 値よりも精度が低くなります。 Single 値に変換された Double 値は、Double 値と一見等しく見えることがありますが、精度の違いにより、多くの場合、実際には等しくありません。 次の例では、同じ除算演算の結果を Double 値と Single 値にそれぞれ代入します。 Single 値を Double にキャストした後で 2 つの値を比較すると、それらの値が等しくないことがわかります。

    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 メソッドを使用します。

さらに、Double 値を使用した算術演算および代入演算の結果は、Double 型の精度損失が原因で、プラットフォームによってわずかに異なる場合があります。 たとえば、リテラルの Double 値を代入した結果は、.NET の 32 ビット版と 64 ビット版で異なる場合があります。 次の例は、リテラル値 -4.42330604244772E-305 と、値が -4.42330604244772E-305 である変数を Double 変数に代入した場合の違いを示しています。 この場合、Parse(String) メソッドの結果は精度損失の影響を受けません。

double value = -4.42330604244772E-305;

double fromLiteral = -4.42330604244772E-305;
double fromVariable = value;
double fromParse = Double.Parse("-4.42330604244772E-305");

Console.WriteLine("Double value from literal: {0,29:R}", fromLiteral);
Console.WriteLine("Double value from variable: {0,28:R}", fromVariable);
Console.WriteLine("Double value from Parse method: {0,24:R}", fromParse);
// On 32-bit versions of the .NET Framework, the output is:
//    Double value from literal:        -4.42330604244772E-305
//    Double value from variable:       -4.42330604244772E-305
//    Double value from Parse method:   -4.42330604244772E-305
//
// On other versions of the .NET Framework, the output is:
//    Double value from literal:      -4.4233060424477198E-305
//    Double value from variable:     -4.4233060424477198E-305
//    Double value from Parse method:   -4.42330604244772E-305
let value = -4.42330604244772E-305

let fromLiteral = -4.42330604244772E-305
let fromVariable = value
let fromParse = Double.Parse "-4.42330604244772E-305"

printfn $"Double value from literal: {fromLiteral,29:R}"
printfn $"Double value from variable: {fromVariable,28:R}"
printfn $"Double value from Parse method: {fromParse,24:R}"
// On 32-bit versions of the .NET Framework, the output is:
//    Double value from literal:        -4.42330604244772E-305
//    Double value from variable:       -4.42330604244772E-305
//    Double value from Parse method:   -4.42330604244772E-305
//
// On other versions of the .NET Framework, the output is:
//    Double value from literal:      -4.4233060424477198E-305
//    Double value from variable:     -4.4233060424477198E-305
//    Double value from Parse method:   -4.42330604244772E-305
Dim value As Double = -4.4233060424477198E-305

Dim fromLiteral As Double = -4.4233060424477198E-305
Dim fromVariable As Double = value
Dim fromParse As Double = Double.Parse("-4.42330604244772E-305")

Console.WriteLine("Double value from literal: {0,29:R}", fromLiteral)
Console.WriteLine("Double value from variable: {0,28:R}", fromVariable)
Console.WriteLine("Double value from Parse method: {0,24:R}", fromParse)
' On 32-bit versions of the .NET Framework, the output is:
'    Double value from literal:        -4.42330604244772E-305
'    Double value from variable:       -4.42330604244772E-305
'    Double value from Parse method:   -4.42330604244772E-305
'
' On other versions of the .NET Framework, the output is:
'    Double value from literal:        -4.4233060424477198E-305
'    Double value from variable:       -4.4233060424477198E-305
'    Double value from Parse method:     -4.42330604244772E-305

等価性のテスト

等しいとみなされるには、2 つの Double 値が同じ値を表現している必要があります。 しかし、値間の精度の違い、一方または両方の値の精度損失により、最下位桁が異なることになり、等しいと想定される浮動小数点値が、多くの場合、実際には等しくありません。 その結果、2 つの値が等しいかどうかを判断するために Equals メソッドを呼び出したり、2 つの CompareTo 値の関係を判断するために Double メソッドを呼び出したりすると、予期せぬ結果になることがよくあります。 次の例では、一見等しい 2 つの Double 値が実際には等しくないことを示しています。これは、最初の値の精度が 15 桁であるのに対し、2 番目の値の精度が 17 桁であるためです。

using System;

public class Example
{
   public static void Main()
   {
      double value1 = .333333333333333;
      double value2 = 1.0/3;
      Console.WriteLine($"{value1:R} = {value2:R}: {value1.Equals(value2)}");
   }
}
// The example displays the following output:
//        0.333333333333333 = 0.33333333333333331: False
open System

let value1 = 0.333333333333333
let value2 = 1. / 3.
printfn $"{value1:R} = {value2:R}: {value1.Equals value2}"
// The example displays the following output:
//        0.333333333333333 = 0.33333333333333331: False
Module Example1
    Public Sub Main()
        Dim value1 As Double = 0.333333333333333
        Dim value2 As Double = 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.333333333333333 = 0.33333333333333331: False

異なるコード パスをたどり、異なる方法で操作された計算値は、多くの場合、実際には等しくありません。 次の例では、1 つ目の Double 値を 2 乗した後、その結果の平方根を計算して元の値を復元しています。 2 番目の Double 値は 3.51 を掛けて 2 乗した後、その結果の平方根を 3.51 で割って元の値を復元しています。 2 つの値は等しく見えますが、Equals(Double) メソッドを呼び出すと、それらの値が等しくないことが示されます。 各 Double 値のすべての有効桁数を表示する結果文字列を返すために「R」標準書式指定文字列を使用すると、2 番目の値が最初の値よりも .0000000000001 小さいことがわかります。

using System;

public class Example1
{
    public static void Main()
    {
        double value1 = 100.10142;
        value1 = Math.Sqrt(Math.Pow(value1, 2));
        double value2 = Math.Pow(value1 * 3.51, 2);
        value2 = Math.Sqrt(value2) / 3.51;
        Console.WriteLine($"{value1} = {value2}: {value1.Equals(value2)}{Environment.NewLine}");
        Console.WriteLine($"{value1:R} = {value2:R}");
    }
}
// The example displays the following output:
//    100.10142 = 100.10142: False
//
//    100.10142 = 100.10141999999999
open System

let value1 = 
    Math.Pow(100.10142, 2)
    |> sqrt

let value2 = 
    let v = pown (value1 * 3.51) 2
    (Math.Sqrt v) / 3.51

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

精度損失が比較結果に影響する可能性がある場合、Equals メソッドや CompareTo メソッドを呼び出す代わりに、次のいずれかの方法を採用できます。

  • Math.Round メソッドを呼び出して、両方の値の有効桁数が同じであることを確認します。 次の例では、前の例を変更してこの方法を使用し、2 つの小数値が等しくなるようにします。

    using System;
    
    public class Example2
    {
        public static void Main()
        {
            double value1 = .333333333333333;
            double value2 = 1.0 / 3;
            int precision = 7;
            value1 = Math.Round(value1, precision);
            value2 = 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 v1 = 0.333333333333333
    let v2 = 1. / 3.
    let precision = 7
    let value1 = Math.Round(v1, precision)
    let value2 = Math.Round(v2, precision)
    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 Double = 0.333333333333333
            Dim value2 As Double = 1 / 3
            Dim precision As Integer = 7
            value1 = Math.Round(value1, precision)
            value2 = 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) メソッドを参照してください。

  • 完全な等価性ではなく、近似的な等価性をテストします。 そのためには、2 つの値が異なっていても等しいとみなせる絶対的な差の許容量を定義するか、または小さい方の値が大きい方の値からどれだけ離れていてもよいかを示す相対的な差の許容量を定義する必要があります。

    警告

    Double.Epsilon は、等価性のテスト時に 2 つの Double 値間の絶対的な差を測るために使用されることがあります。 しかし、Double.Epsilon は、値がゼロである Double に対して加算または減算できる最小の値を示します。 ほとんどの正および負の Double 値に対して、Double.Epsilon の値は小さすぎて検出できません。 したがって、値がゼロの場合を除き、等価性のテストでの Double.Epsilon の使用はお勧めしません。

    次の例では、後者の方法 (相対的な差の許容量) を使用して、2 つの値の相対的な差をテストする IsApproximatelyEqual メソッドを定義しています。 また、IsApproximatelyEqual メソッドと Equals(Double) メソッドの呼び出し結果を比較対照しています。

    using System;
    
    public class Example3
    {
        public static void Main()
        {
            double one1 = .1 * 10;
            double one2 = 0;
            for (int ctr = 1; ctr <= 10; ctr++)
                one2 += .1;
    
            Console.WriteLine($"{one1:R} = {one2:R}: {one1.Equals(one2)}");
            Console.WriteLine($"{one1:R} is approximately equal to {one2:R}: {IsApproximatelyEqual(one1, one2, .000000001)}");
        }
    
        static bool IsApproximatelyEqual(double value1, double value2, double 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 = 0.99999999999999989: False
    //       1 is approximately equal to 0.99999999999999989: True
    
    open System
    
    let isApproximatelyEqual (value1: double) (value2: double) (epsilon: double) =
        // If they are equal anyway, just return True.
        if value1.Equals value2 then 
            true
        else
            // Handle NaN, Infinity.
            if Double.IsInfinity value1 || Double.IsNaN value1 then 
                value1.Equals value2
            elif Double.IsInfinity value2 || Double.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.1 * 10.
    let mutable one2 = 0.
    for _ = 1 to 10 do
        one2 <- one2 + 0.1
    
    printfn $"{one1:R} = {one2:R}: {one1.Equals one2}"
    printfn $"{one1:R} is approximately equal to {one2:R}: {isApproximatelyEqual one1 one2 0.000000001}"
    
    // The example displays the following output:
    //       1 = 0.99999999999999989: False
    //       1 is approximately equal to 0.99999999999999989: True
    
    Module Example4
        Public Sub Main()
            Dim one1 As Double = 0.1 * 10
            Dim one2 As Double = 0
            For ctr As Integer = 1 To 10
                one2 += 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.000000001))
        End Sub
    
        Function IsApproximatelyEqual(value1 As Double, value2 As Double,
                                     epsilon As Double) As Boolean
            ' If they are equal anyway, just return True.
            If value1.Equals(value2) Then Return True
    
            ' Handle NaN, Infinity.
            If Double.IsInfinity(value1) Or Double.IsNaN(value1) Then
                Return value1.Equals(value2)
            ElseIf Double.IsInfinity(value2) Or Double.IsNaN(value2) Then
                Return value1.Equals(value2)
            End If
    
            ' Handle zero to avoid division by zero
            Dim divisor As Double = 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 = 0.99999999999999989: False
    '       1 is approximately equal to 0.99999999999999989: True
    

浮動小数点値と例外

オーバーフローやゼロ除算などの不正な操作の場合に例外をスローする整数型の演算とは異なり、浮動小数点値の演算では例外はスローされません。 代わりに、例外的な状況では、浮動小数点演算の結果はゼロ、正の無限大、負の無限大、または非数 (NaN) になります。

  • 浮動小数点演算の結果が、変換先の形式で表現するには小さすぎる場合、結果はゼロになります。 このような結果になる可能性があるのは、次の例に示すように、非常に小さい 2 つの数値を乗算した場合などです。

    using System;
    
    public class Example6
    {
        public static void Main()
        {
            Double value1 = 1.1632875981534209e-225;
            Double value2 = 9.1642346778e-175;
            Double result = value1 * value2;
            Console.WriteLine($"{value1} * {value2} = {result}");
            Console.WriteLine($"{result} = 0: {result.Equals(0.0)}");
        }
    }
    // The example displays the following output:
    //       1.16328759815342E-225 * 9.1642346778E-175 = 0
    //       0 = 0: True
    
    let value1 = 1.1632875981534209e-225
    let value2 = 9.1642346778e-175
    let result = value1 * value2
    printfn $"{value1} * {value2} = {result}"
    printfn $"{result} = 0: {result.Equals 0.0}"
    // The example displays the following output:
    //       1.16328759815342E-225 * 9.1642346778E-175 = 0
    //       0 = 0: True
    
    Module Example7
        Public Sub Main()
            Dim value1 As Double = 1.1632875981534209E-225
            Dim value2 As Double = 9.1642346778E-175
            Dim result As Double = value1 * value2
            Console.WriteLine("{0} * {1} = {2}", value1, value2, result)
            Console.WriteLine("{0} = 0: {1}", result, result.Equals(0.0))
        End Sub
    End Module
    ' The example displays the following output:
    '       1.16328759815342E-225 * 9.1642346778E-175 = 0
    '       0 = 0: True
    
  • 浮動小数点演算の結果の絶対値が変換先の形式の範囲を超える場合、演算の結果は、結果の符号に応じて PositiveInfinity または NegativeInfinity となります。 次の例に示すように、Double.MaxValue を超える演算結果は PositiveInfinityDouble.MinValue を下回る演算結果は NegativeInfinity になります。

    using System;
    
    public class Example7
    {
        public static void Main()
        {
            Double value1 = 4.565e153;
            Double value2 = 6.9375e172;
            Double result = value1 * value2;
            Console.WriteLine($"PositiveInfinity: {Double.IsPositiveInfinity(result)}");
            Console.WriteLine($"NegativeInfinity: {Double.IsNegativeInfinity(result)}{Environment.NewLine}");
    
            value1 = -value1;
            result = value1 * value2;
            Console.WriteLine($"PositiveInfinity: {Double.IsPositiveInfinity(result)}");
            Console.WriteLine($"NegativeInfinity: {Double.IsNegativeInfinity(result)}");
        }
    }
    
    // The example displays the following output:
    //       PositiveInfinity: True
    //       NegativeInfinity: False
    //
    //       PositiveInfinity: False
    //       NegativeInfinity: True
    
    open System
    
    let value1 = 4.565e153
    let value2 = 6.9375e172
    let result = value1 * value2
    printfn $"PositiveInfinity: {Double.IsPositiveInfinity result}"
    printfn $"NegativeInfinity: {Double.IsNegativeInfinity result}\n"
    
    let value3 = - value1
    let result2 = value2 * value3
    printfn $"PositiveInfinity: {Double.IsPositiveInfinity result2}"
    printfn $"NegativeInfinity: {Double.IsNegativeInfinity result2}"
    
    // The example displays the following output:
    //       PositiveInfinity: True
    //       NegativeInfinity: False
    //
    //       PositiveInfinity: False
    //       NegativeInfinity: True
    
    Module Example8
        Public Sub Main()
            Dim value1 As Double = 4.565E+153
            Dim value2 As Double = 6.9375E+172
            Dim result As Double = value1 * value2
            Console.WriteLine("PositiveInfinity: {0}",
                             Double.IsPositiveInfinity(result))
            Console.WriteLine("NegativeInfinity: {0}",
                            Double.IsNegativeInfinity(result))
            Console.WriteLine()
            value1 = -value1
            result = value1 * value2
            Console.WriteLine("PositiveInfinity: {0}",
                             Double.IsPositiveInfinity(result))
            Console.WriteLine("NegativeInfinity: {0}",
                            Double.IsNegativeInfinity(result))
        End Sub
    End Module
    ' The example displays the following output:
    '       PositiveInfinity: True
    '       NegativeInfinity: False
    '       
    '       PositiveInfinity: False
    '       NegativeInfinity: True
    

    PositiveInfinity は正の被除数でゼロ除算を行った結果を示し、NegativeInfinity は負の被除数でゼロ除算を行った結果を示します。

  • 浮動小数点演算が無効な場合、演算の結果は NaN になります。 たとえば、次の演算では NaN が返されます。

    • 0 で除算し、被除数を 0 にします。 なお、その他のゼロ除算のケースでは、結果は PositiveInfinity または NegativeInfinity のいずれかになります。

    • 無効な入力値に対する浮動小数点演算。 たとえば、負の値に対して Math.Sqrt メソッドを呼び出した場合や、1 より大きい値または -1 より小さい値に対して NaN メソッドを呼び出した場合には、Math.Acos が返されます。

    • Double.NaN を引数とする任意の演算。

型の変換

Double 構造体には明示的または暗黙的な変換演算子は定義されておらず、代わりに変換はコンパイラによって実装されています。

任意のプリミティブ数値型の値を Double に変換するのは拡大変換であり、コンパイラが明示的に要求する場合を除き、明示的なキャスト演算子や変換メソッドの呼び出しは不要です。 たとえば、C# コンパイラは Decimal から Double への変換にキャスト演算子を要求しますが、Visual Basic コンパイラは要求しません。 次の例では、他のプリミティブ数値型の最小値または最大値を Double に変換しています。

dynamic[] values = { Byte.MinValue, Byte.MaxValue, Decimal.MinValue,
                   Decimal.MaxValue, Int16.MinValue, Int16.MaxValue,
                   Int32.MinValue, Int32.MaxValue, Int64.MinValue,
                   Int64.MaxValue, SByte.MinValue, SByte.MaxValue,
                   Single.MinValue, Single.MaxValue, UInt16.MinValue,
                   UInt16.MaxValue, UInt32.MinValue, UInt32.MaxValue,
                   UInt64.MinValue, UInt64.MaxValue };
double dblValue;
foreach (dynamic value in values)
{
    if (value.GetType() == typeof(decimal))
        dblValue = (double)value;
    else
        dblValue = value;
    Console.WriteLine($"{value} ({value.GetType().Name}) --> " +
        $"{dblValue:R} ({dblValue.GetType().Name})");
}

// The example displays the following output:
//    0 (Byte) --> 0 (Double)
//    255 (Byte) --> 255 (Double)
//    -79228162514264337593543950335 (Decimal) --> -7.9228162514264338E+28 (Double)
//    79228162514264337593543950335 (Decimal) --> 7.9228162514264338E+28 (Double)
//    -32768 (Int16) --> -32768 (Double)
//    32767 (Int16) --> 32767 (Double)
//    -2147483648 (Int32) --> -2147483648 (Double)
//    2147483647 (Int32) --> 2147483647 (Double)
//    -9223372036854775808 (Int64) --> -9.2233720368547758E+18 (Double)
//    9223372036854775807 (Int64) --> 9.2233720368547758E+18 (Double)
//    -128 (SByte) --> -128 (Double)
//    127 (SByte) --> 127 (Double)
//    -3.402823E+38 (Single) --> -3.4028234663852886E+38 (Double)
//    3.402823E+38 (Single) --> 3.4028234663852886E+38 (Double)
//    0 (UInt16) --> 0 (Double)
//    65535 (UInt16) --> 65535 (Double)
//    0 (UInt32) --> 0 (Double)
//    4294967295 (UInt32) --> 4294967295 (Double)
//    0 (UInt64) --> 0 (Double)
//    18446744073709551615 (UInt64) --> 1.8446744073709552E+19 (Double)
open System

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

for value in values do
    let dblValue = value :?> double
    printfn $"{value} ({value.GetType().Name}) --> {dblValue:R} ({dblValue.GetType().Name})"
// The example displays the following output:
//    0 (Byte) --> 0 (Double)
//    255 (Byte) --> 255 (Double)
//    -79228162514264337593543950335 (Decimal) --> -7.9228162514264338E+28 (Double)
//    79228162514264337593543950335 (Decimal) --> 7.9228162514264338E+28 (Double)
//    -32768 (Int16) --> -32768 (Double)
//    32767 (Int16) --> 32767 (Double)
//    -2147483648 (Int32) --> -2147483648 (Double)
//    2147483647 (Int32) --> 2147483647 (Double)
//    -9223372036854775808 (Int64) --> -9.2233720368547758E+18 (Double)
//    9223372036854775807 (Int64) --> 9.2233720368547758E+18 (Double)
//    -128 (SByte) --> -128 (Double)
//    127 (SByte) --> 127 (Double)
//    -3.402823E+38 (Single) --> -3.4028234663852886E+38 (Double)
//    3.402823E+38 (Single) --> 3.4028234663852886E+38 (Double)
//    0 (UInt16) --> 0 (Double)
//    65535 (UInt16) --> 65535 (Double)
//    0 (UInt32) --> 0 (Double)
//    4294967295 (UInt32) --> 4294967295 (Double)
//    0 (UInt64) --> 0 (Double)
//    18446744073709551615 (UInt64) --> 1.8446744073709552E+19 (Double)
Module Example5
    Public Sub Main()
        Dim values() As Object = {Byte.MinValue, Byte.MaxValue, Decimal.MinValue,
                                 Decimal.MaxValue, Int16.MinValue, Int16.MaxValue,
                                 Int32.MinValue, Int32.MaxValue, Int64.MinValue,
                                 Int64.MaxValue, SByte.MinValue, SByte.MaxValue,
                                 Single.MinValue, Single.MaxValue, UInt16.MinValue,
                                 UInt16.MaxValue, UInt32.MinValue, UInt32.MaxValue,
                                 UInt64.MinValue, UInt64.MaxValue}
        Dim dblValue As Double
        For Each value In values
            dblValue = value
            Console.WriteLine("{0} ({1}) --> {2:R} ({3})",
                           value, value.GetType().Name,
                           dblValue, dblValue.GetType().Name)
        Next
    End Sub
End Module
' The example displays the following output:
'    0 (Byte) --> 0 (Double)
'    255 (Byte) --> 255 (Double)
'    -79228162514264337593543950335 (Decimal) --> -7.9228162514264338E+28 (Double)
'    79228162514264337593543950335 (Decimal) --> 7.9228162514264338E+28 (Double)
'    -32768 (Int16) --> -32768 (Double)
'    32767 (Int16) --> 32767 (Double)
'    -2147483648 (Int32) --> -2147483648 (Double)
'    2147483647 (Int32) --> 2147483647 (Double)
'    -9223372036854775808 (Int64) --> -9.2233720368547758E+18 (Double)
'    9223372036854775807 (Int64) --> 9.2233720368547758E+18 (Double)
'    -128 (SByte) --> -128 (Double)
'    127 (SByte) --> 127 (Double)
'    -3.402823E+38 (Single) --> -3.4028234663852886E+38 (Double)
'    3.402823E+38 (Single) --> 3.4028234663852886E+38 (Double)
'    0 (UInt16) --> 0 (Double)
'    65535 (UInt16) --> 65535 (Double)
'    0 (UInt32) --> 0 (Double)
'    4294967295 (UInt32) --> 4294967295 (Double)
'    0 (UInt64) --> 0 (Double)
'    18446744073709551615 (UInt64) --> 1.8446744073709552E+19 (Double)

さらに、SingleSingle.NaNSingle.PositiveInfinity など Single.NegativeInfinity の値は、それぞれ Double.NaNDouble.PositiveInfinityDouble.NegativeInfinity に変換されます。

一部の数値型の値を Double 値に変換すると、精度が失われることがあります。 この例が示すように、DecimalInt64UInt64 の値を Double 値に変換すると、精度が失われる可能性があります。

Double 値を他のプリミティブ数値データ型の値に変換するのは縮小変換であり、キャスト演算子 (C# の場合)、変換メソッド (Visual Basic の場合)、または Convert メソッドの呼び出しが必要です。 変換先のデータ型の範囲 (その型の MinValue プロパティと MaxValue プロパティによって定義される) から外れる値は、次の表に示すように処理されます。

変換後の型 結果
任意の整数型 変換が checked コンテキストで行われた場合は、OverflowException 例外が発生します。

変換が unchecked コンテキスト (C# の既定) で行われた場合、変換操作自体は成功しますが、値はオーバーフローします。
Decimal OverflowException 例外。
Single 負の値の場合は Single.NegativeInfinity

正の値の場合は Single.PositiveInfinity

さらに、Double.NaNDouble.PositiveInfinityDouble.NegativeInfinity は、checked コンテキストで整数に変換しようとすると OverflowException をスローしますが、unchecked コンテキストで整数に変換されるとオーバーフローします。 Decimal への変換では、常に OverflowExceptionがスローされます。 Single への変換では、それぞれ Single.NaNSingle.PositiveInfinitySingle.NegativeInfinity に変換されます。

Double 値を他の数値型に変換すると、精度が失われる可能性があります。 いずれかの整数型に変換する場合、例の出力が示すように、Double 値が丸められる (Visual Basic の場合) か切り捨てられる (C# の場合) ことで、小数部分が失われます。 Decimal 値や Single 値への変換では、元の Double 値を変換先のデータ型で正確に表現できない場合があります。

次の例では、いくつかの Double 値を他の複数の数値型に変換します。 変換は、Visual Basic (デフォルトで)、C# (checked キーワードを使用している場合)、F# (Checked モジュールを使用している場合) では、checked コンテキストで行われます。 この例の出力は、checked コンテキストと unchecked コンテキストの両方での変換結果を示しています。 Visual Basic では /removeintchecks+ コンパイラ スイッチを使用してコンパイルするか、C# では checked ステートメントをコメントアウトするか、F# では open Checked ステートメントをコメントアウトすることで、変換を unchecked コンテキストで行うことができます。

using System;

public class Example5
{
    public static void Main()
    {
        Double[] values = { Double.MinValue, -67890.1234, -12345.6789,
                          12345.6789, 67890.1234, Double.MaxValue,
                          Double.NaN, Double.PositiveInfinity,
                          Double.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.");
                }
                try
                {
                    Single sValue = (float)value;
                    Console.WriteLine($"{value} ({value.GetType().Name}) --> {sValue} ({sValue.GetType().Name})");
                }
                catch (OverflowException)
                {
                    Console.WriteLine($"Unable to convert {value} to Single.");
                }
                Console.WriteLine();
            }
        }
    }
}
// The example displays the following output for conversions performed
// in a checked context:
//       Unable to convert -1.79769313486232E+308 to Int64.
//       Unable to convert -1.79769313486232E+308 to UInt64.
//       Unable to convert -1.79769313486232E+308 to Decimal.
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//
//       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       Unable to convert -67890.1234 to UInt64.
//       -67890.1234 (Double) --> -67890.1234 (Decimal)
//       -67890.1234 (Double) --> -67890.13 (Single)
//
//       -12345.6789 (Double) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       Unable to convert -12345.6789 to UInt64.
//       -12345.6789 (Double) --> -12345.6789 (Decimal)
//       -12345.6789 (Double) --> -12345.68 (Single)
//
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (Int64)
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (UInt64)
//       12345.6789 (Double) --> 12345.6789 (Decimal)
//       12345.6789 (Double) --> 12345.68 (Single)
//
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
//       67890.1234 (Double) --> 67890.1234 (Decimal)
//       67890.1234 (Double) --> 67890.13 (Single)
//
//       Unable to convert 1.79769313486232E+308 to Int64.
//       Unable to convert 1.79769313486232E+308 to UInt64.
//       Unable to convert 1.79769313486232E+308 to Decimal.
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//
//       Unable to convert NaN to Int64.
//       Unable to convert NaN to UInt64.
//       Unable to convert NaN to Decimal.
//       NaN (Double) --> NaN (Single)
//
//       Unable to convert Infinity to Int64.
//       Unable to convert Infinity to UInt64.
//       Unable to convert Infinity to Decimal.
//       Infinity (Double) --> Infinity (Single)
//
//       Unable to convert -Infinity to Int64.
//       Unable to convert -Infinity to UInt64.
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Double) --> -Infinity (Single)
// The example displays the following output for conversions performed
// in an unchecked context:
//       -1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -1.79769313486232E+308 (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -1.79769313486232E+308 to Decimal.
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//
//       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       -67890.1234 (Double) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
//       -67890.1234 (Double) --> -67890.1234 (Decimal)
//       -67890.1234 (Double) --> -67890.13 (Single)
//
//       -12345.6789 (Double) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       -12345.6789 (Double) --> 18446744073709539271 (0xFFFFFFFFFFFFCFC7) (UInt64)
//       -12345.6789 (Double) --> -12345.6789 (Decimal)
//       -12345.6789 (Double) --> -12345.68 (Single)
//
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (Int64)
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (UInt64)
//       12345.6789 (Double) --> 12345.6789 (Decimal)
//       12345.6789 (Double) --> 12345.68 (Single)
//
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
//       67890.1234 (Double) --> 67890.1234 (Decimal)
//       67890.1234 (Double) --> 67890.13 (Single)
//
//       1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       1.79769313486232E+308 (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert 1.79769313486232E+308 to Decimal.
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//
//       NaN (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       NaN (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert NaN to Decimal.
//       NaN (Double) --> NaN (Single)
//
//       Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       Infinity (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert Infinity to Decimal.
//       Infinity (Double) --> Infinity (Single)
//
//       -Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -Infinity (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Double) --> -Infinity (Single)
open System
open Checked

let values = 
    [| Double.MinValue; -67890.1234; -12345.6789
       12345.6789; 67890.1234; Double.MaxValue
       Double.NaN; Double.PositiveInfinity;
       Double.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."
    try
        let sValue = float32 value
        printfn $"{value} ({value.GetType().Name}) --> {sValue} ({sValue.GetType().Name})"
    with :? OverflowException ->
        printfn $"Unable to convert {value} to Single."
    printfn ""
// The example displays the following output for conversions performed
// in a checked context:
//       Unable to convert -1.79769313486232E+308 to Int64.
//       Unable to convert -1.79769313486232E+308 to UInt64.
//       Unable to convert -1.79769313486232E+308 to Decimal.
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//
//       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       Unable to convert -67890.1234 to UInt64.
//       -67890.1234 (Double) --> -67890.1234 (Decimal)
//       -67890.1234 (Double) --> -67890.13 (Single)
//
//       -12345.6789 (Double) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       Unable to convert -12345.6789 to UInt64.
//       -12345.6789 (Double) --> -12345.6789 (Decimal)
//       -12345.6789 (Double) --> -12345.68 (Single)
//
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (Int64)
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (UInt64)
//       12345.6789 (Double) --> 12345.6789 (Decimal)
//       12345.6789 (Double) --> 12345.68 (Single)
//
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
//       67890.1234 (Double) --> 67890.1234 (Decimal)
//       67890.1234 (Double) --> 67890.13 (Single)
//
//       Unable to convert 1.79769313486232E+308 to Int64.
//       Unable to convert 1.79769313486232E+308 to UInt64.
//       Unable to convert 1.79769313486232E+308 to Decimal.
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//
//       Unable to convert NaN to Int64.
//       Unable to convert NaN to UInt64.
//       Unable to convert NaN to Decimal.
//       NaN (Double) --> NaN (Single)
//
//       Unable to convert Infinity to Int64.
//       Unable to convert Infinity to UInt64.
//       Unable to convert Infinity to Decimal.
//       Infinity (Double) --> Infinity (Single)
//
//       Unable to convert -Infinity to Int64.
//       Unable to convert -Infinity to UInt64.
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Double) --> -Infinity (Single)
// The example displays the following output for conversions performed
// in an unchecked context:
//       -1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -1.79769313486232E+308 (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -1.79769313486232E+308 to Decimal.
//       -1.79769313486232E+308 (Double) --> -Infinity (Single)
//
//       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
//       -67890.1234 (Double) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
//       -67890.1234 (Double) --> -67890.1234 (Decimal)
//       -67890.1234 (Double) --> -67890.13 (Single)
//
//       -12345.6789 (Double) --> -12345 (0xFFFFFFFFFFFFCFC7) (Int64)
//       -12345.6789 (Double) --> 18446744073709539271 (0xFFFFFFFFFFFFCFC7) (UInt64)
//       -12345.6789 (Double) --> -12345.6789 (Decimal)
//       -12345.6789 (Double) --> -12345.68 (Single)
//
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (Int64)
//       12345.6789 (Double) --> 12345 (0x0000000000003039) (UInt64)
//       12345.6789 (Double) --> 12345.6789 (Decimal)
//       12345.6789 (Double) --> 12345.68 (Single)
//
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
//       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
//       67890.1234 (Double) --> 67890.1234 (Decimal)
//       67890.1234 (Double) --> 67890.13 (Single)
//
//       1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       1.79769313486232E+308 (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert 1.79769313486232E+308 to Decimal.
//       1.79769313486232E+308 (Double) --> Infinity (Single)
//
//       NaN (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       NaN (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert NaN to Decimal.
//       NaN (Double) --> NaN (Single)
//
//       Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       Infinity (Double) --> 0 (0x0000000000000000) (UInt64)
//       Unable to convert Infinity to Decimal.
//       Infinity (Double) --> Infinity (Single)
//
//       -Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
//       -Infinity (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
//       Unable to convert -Infinity to Decimal.
//       -Infinity (Double) --> -Infinity (Single)
Module Example6
    Public Sub Main()
        Dim values() As Double = {Double.MinValue, -67890.1234, -12345.6789,
                                 12345.6789, 67890.1234, Double.MaxValue,
                                 Double.NaN, Double.PositiveInfinity,
                                 Double.NegativeInfinity}
        For Each value In values
            Try
                Dim lValue As Int64 = 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
            Try
                Dim sValue As Single = CSng(value)
                Console.WriteLine("{0} ({1}) --> {2} ({3})",
                               value, value.GetType().Name,
                               sValue, sValue.GetType().Name)
            Catch e As OverflowException
                Console.WriteLine("Unable to convert {0} to Single.", value)
            End Try
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output for conversions performed
' in a checked context:
'       Unable to convert -1.79769313486232E+308 to Int64.
'       Unable to convert -1.79769313486232E+308 to UInt64.
'       Unable to convert -1.79769313486232E+308 to Decimal.
'       -1.79769313486232E+308 (Double) --> -Infinity (Single)
'
'       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
'       Unable to convert -67890.1234 to UInt64.
'       -67890.1234 (Double) --> -67890.1234 (Decimal)
'       -67890.1234 (Double) --> -67890.13 (Single)
'
'       -12345.6789 (Double) --> -12346 (0xFFFFFFFFFFFFCFC6) (Int64)
'       Unable to convert -12345.6789 to UInt64.
'       -12345.6789 (Double) --> -12345.6789 (Decimal)
'       -12345.6789 (Double) --> -12345.68 (Single)
'
'       12345.6789 (Double) --> 12346 (0x000000000000303A) (Int64)
'       12345.6789 (Double) --> 12346 (0x000000000000303A) (UInt64)
'       12345.6789 (Double) --> 12345.6789 (Decimal)
'       12345.6789 (Double) --> 12345.68 (Single)
'
'       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
'       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
'       67890.1234 (Double) --> 67890.1234 (Decimal)
'       67890.1234 (Double) --> 67890.13 (Single)
'
'       Unable to convert 1.79769313486232E+308 to Int64.
'       Unable to convert 1.79769313486232E+308 to UInt64.
'       Unable to convert 1.79769313486232E+308 to Decimal.
'       1.79769313486232E+308 (Double) --> Infinity (Single)
'
'       Unable to convert NaN to Int64.
'       Unable to convert NaN to UInt64.
'       Unable to convert NaN to Decimal.
'       NaN (Double) --> NaN (Single)
'
'       Unable to convert Infinity to Int64.
'       Unable to convert Infinity to UInt64.
'       Unable to convert Infinity to Decimal.
'       Infinity (Double) --> Infinity (Single)
'
'       Unable to convert -Infinity to Int64.
'       Unable to convert -Infinity to UInt64.
'       Unable to convert -Infinity to Decimal.
'       -Infinity (Double) --> -Infinity (Single)
' The example displays the following output for conversions performed
' in an unchecked context:
'       -1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       -1.79769313486232E+308 (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
'       Unable to convert -1.79769313486232E+308 to Decimal.
'       -1.79769313486232E+308 (Double) --> -Infinity (Single)
'
'       -67890.1234 (Double) --> -67890 (0xFFFFFFFFFFFEF6CE) (Int64)
'       -67890.1234 (Double) --> 18446744073709483726 (0xFFFFFFFFFFFEF6CE) (UInt64)
'       -67890.1234 (Double) --> -67890.1234 (Decimal)
'       -67890.1234 (Double) --> -67890.13 (Single)
'
'       -12345.6789 (Double) --> -12346 (0xFFFFFFFFFFFFCFC6) (Int64)
'       -12345.6789 (Double) --> 18446744073709539270 (0xFFFFFFFFFFFFCFC6) (UInt64)
'       -12345.6789 (Double) --> -12345.6789 (Decimal)
'       -12345.6789 (Double) --> -12345.68 (Single)
'
'       12345.6789 (Double) --> 12346 (0x000000000000303A) (Int64)
'       12345.6789 (Double) --> 12346 (0x000000000000303A) (UInt64)
'       12345.6789 (Double) --> 12345.6789 (Decimal)
'       12345.6789 (Double) --> 12345.68 (Single)
'
'       67890.1234 (Double) --> 67890 (0x0000000000010932) (Int64)
'       67890.1234 (Double) --> 67890 (0x0000000000010932) (UInt64)
'       67890.1234 (Double) --> 67890.1234 (Decimal)
'       67890.1234 (Double) --> 67890.13 (Single)
'
'       1.79769313486232E+308 (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       1.79769313486232E+308 (Double) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert 1.79769313486232E+308 to Decimal.
'       1.79769313486232E+308 (Double) --> Infinity (Single)
'
'       NaN (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       NaN (Double) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert NaN to Decimal.
'       NaN (Double) --> NaN (Single)
'
'       Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       Infinity (Double) --> 0 (0x0000000000000000) (UInt64)
'       Unable to convert Infinity to Decimal.
'       Infinity (Double) --> Infinity (Single)
'
'       -Infinity (Double) --> -9223372036854775808 (0x8000000000000000) (Int64)
'       -Infinity (Double) --> 9223372036854775808 (0x8000000000000000) (UInt64)
'       Unable to convert -Infinity to Decimal.
'       -Infinity (Double) --> -Infinity (Single)

数値型の変換の詳細については、「.NET での型変換」および「型変換の表」を参照してください。

浮動小数点機能

Double 構造体および関連する型には、次の領域での操作を実行するメソッドが用意されています。

  • 値の比較Equals メソッドを呼び出して 2 つの Double 値が等しいかどうかを判断したり、CompareTo メソッドを呼び出して 2 つの値の関係を判断したりできます。

    Double 構造体では、すべての比較演算子もサポートしています。 たとえば、等価性または非等価性をテストしたり、ある値が別の値より大きいか等しいかを判断したりすることができます。 オペランドのいずれかが Double 以外の数値型である場合、比較前に Double に変換されます。

    警告

    精度の違いから、等しいと想定される 2 つの Double 値が実際には等しくないことがあり、比較の結果に影響します。 2 つの 値の比較の詳細については、「Double」を参照してください。

    IsNaNIsInfinityIsPositiveInfinityIsNegativeInfinity の各メソッドを呼び出して、これらの特殊な値であるかどうかをテストすることもできます。

  • 算術演算。 加算、減算、乗算、除算などの一般的な算術演算は、Double のメソッドではなく、言語コンパイラおよび共通中間言語 (CIL) の命令によって実装されます。 算術演算のオペランドのいずれかが Double 以外の数値型である場合、演算の実行前に Double に変換されます。 演算の結果も Double 値になります。

    その他の算術演算は static クラスの Shared (Visual Basic では System.Math) メソッドを呼び出すことで実行できます。 これには、算術 (Math.AbsMath.SignMath.Sqrt など)、幾何学 (Math.CosMath.Sin など)、および微積分 (Math.Log など) で一般的に使用される追加のメソッドが含まれています。

    Double 値の個々のビットを操作することもできます。 BitConverter.DoubleToInt64Bits メソッドは Double 値のビット パターンを 64 ビット整数として保持します。 BitConverter.GetBytes(Double) メソッドはそのビット パターンをバイト配列で返します。

  • 丸め。 丸めは、浮動小数点表現と精度の問題に起因する値間の差異の影響を軽減するための手法としてよく使用されます。 Double メソッドを呼び出して、Math.Round 値を丸めることができます。

  • 書式設定Double メソッドを呼び出すか、複合書式指定機能を使用して、ToString 値をその文字列表現に変換できます。 書式指定文字列が浮動小数点値の文字列表現をどのように制御するかの詳細については、「標準の数値書式指定文字列」および「カスタム数値書式指定文字列」を参照してください。

  • 文字列の解析Double メソッドまたは Parse メソッドのいずれかを呼び出すことで、浮動小数点値の文字列表現を TryParse 値に変換できます。 解析操作が失敗した場合、Parse メソッドが例外をスローするのに対して、TryParse メソッドは false を返します。

  • 型変換Double 構造体には IConvertible インターフェイスが明示的に実装されているため、.NET 環境での標準データ型間の相互変換が可能になっています。 言語コンパイラは、他のすべての標準数値型の値から Double 値への暗黙的な変換もサポートしています。 任意の標準数値型の値を Double に変換するのは拡大変換であり、ユーザーがキャスト演算子や変換メソッドを使用する必要はありません。

    ただし、Int64 および Single の値を変換すると、精度が失われる可能性があります。 次の表に、これらの型ごとの精度の違いを示します。

    タイプ 最大精度 内部精度
    Double 15 十七
    Int64 19 桁の 10 進精度 19 桁の 10 進精度
    Single 7 桁の 10 進精度 9 桁の 10 進精度

    精度の問題は、Single 値が Double 値に変換される際に最もよく発生しています。 次の例では、同じ除算演算によって生成された 2 つの値が等しくありません。これは、一方の値が Double に変換された単精度浮動小数点値であるためです。

    using System;
    
    public class Example13
    {
        public static void Main()
        {
            Double value = .1;
            Double result1 = value * 10;
            Double result2 = 0;
            for (int ctr = 1; ctr <= 10; ctr++)
                result2 += value;
    
            Console.WriteLine($".1 * 10:           {result1:R}");
            Console.WriteLine($".1 Added 10 times: {result2:R}");
        }
    }
    // The example displays the following output:
    //       .1 * 10:           1
    //       .1 Added 10 times: 0.99999999999999989
    
    let value = 0.1
    let result1 = value * 10.
    let mutable result2 = 0.
    for i = 1 to 10 do
        result2 <- result2 + value
    
    printfn $".1 * 10:           {result1:R}"
    printfn $".1 Added 10 times: {result2:R}"
    // The example displays the following output:
    //       .1 * 10:           1
    //       .1 Added 10 times: 0.99999999999999989
    
    Module Example14
        Public Sub Main()
            Dim value As Double = 0.1
            Dim result1 As Double = value * 10
            Dim result2 As Double
            For ctr As Integer = 1 To 10
                result2 += value
            Next
            Console.WriteLine(".1 * 10:           {0:R}", result1)
            Console.WriteLine(".1 Added 10 times: {0:R}", result2)
        End Sub
    End Module
    ' The example displays the following output:
    '       .1 * 10:           1
    '       .1 Added 10 times: 0.99999999999999989
    

例示

次のコード例に Double の使用方法を示します。

// The Temperature class stores the temperature as a Double
// and delegates most of the functionality to the Double
// implementation.
public class Temperature : IComparable, IFormattable
{
    // IComparable.CompareTo implementation.
    public int CompareTo(object obj) {
        if (obj == null) return 1;

        Temperature temp = obj as Temperature;
        if (obj != null)
            return m_value.CompareTo(temp.m_value);
        else
            throw new ArgumentException("object is not a Temperature");	
    }

    // IFormattable.ToString implementation.
    public string ToString(string format, IFormatProvider provider) {
        if( format != null ) {
            if( format.Equals("F") ) {
                return String.Format("{0}'F", this.Value.ToString());
            }
            if( format.Equals("C") ) {
                return String.Format("{0}'C", this.Celsius.ToString());
            }
        }

        return m_value.ToString(format, provider);
    }

    // Parses the temperature from a string in the form
    // [ws][sign]digits['F|'C][ws]
    public static Temperature Parse(string s, NumberStyles styles, IFormatProvider provider) {
        Temperature temp = new Temperature();

        if( s.TrimEnd(null).EndsWith("'F") ) {
            temp.Value = Double.Parse( s.Remove(s.LastIndexOf('\''), 2), styles, provider);
        }
        else if( s.TrimEnd(null).EndsWith("'C") ) {
            temp.Celsius = Double.Parse( s.Remove(s.LastIndexOf('\''), 2), styles, provider);
        }
        else {
            temp.Value = Double.Parse(s, styles, provider);
        }

        return temp;
    }

    // The value holder
    protected double m_value;

    public double Value {
        get {
            return m_value;
        }
        set {
            m_value = value;
        }
    }

    public double Celsius {
        get {
            return (m_value-32.0)/1.8;
        }
        set {
            m_value = 1.8*value+32.0;
        }
    }
}
// The Temperature class stores the temperature as a Double
// and delegates most of the functionality to the Double
// implementation.
type Temperature() =
    member val Value = 0. with get, set

    member this.Celsius
        with get () = (this.Value - 32.) / 1.8
        and set (value) =
            this.Value <- 1.8 * value + 32.

    // Parses the temperature from a string in the form
    // [ws][sign]digits['F|'C][ws]
    static member Parse(s: string, styles: NumberStyles, provider: IFormatProvider) =
        let temp = Temperature()

        if s.TrimEnd(null).EndsWith "'F" then
            temp.Value <- Double.Parse(s.Remove(s.LastIndexOf '\'', 2), styles, provider)
        elif s.TrimEnd(null).EndsWith "'C" then
            temp.Celsius <- Double.Parse(s.Remove(s.LastIndexOf '\'', 2), styles, provider)
        else
            temp.Value <- Double.Parse(s, styles, provider)
        temp

    interface IComparable with
        // IComparable.CompareTo implementation.
        member this.CompareTo(obj: obj) =
            match obj with 
            | null -> 1
            | :? Temperature as temp ->
                this.Value.CompareTo temp.Value
            | _ ->
                invalidArg "obj" "object is not a Temperature"

    interface IFormattable with
        // IFormattable.ToString implementation.
        member this.ToString(format: string, provider: IFormatProvider) =
            match format with
            | "F" ->
                $"{this.Value}'F"
            | "C" ->
                $"{this.Celsius}'C"
            | _ ->
                this.Value.ToString(format, provider)
' Temperature class stores the value as Double
' and delegates most of the functionality 
' to the Double implementation.
Public Class Temperature
    Implements IComparable, IFormattable

    Public Overloads Function CompareTo(ByVal obj As Object) As Integer _
        Implements IComparable.CompareTo

        If TypeOf obj Is Temperature Then
            Dim temp As Temperature = CType(obj, Temperature)

            Return m_value.CompareTo(temp.m_value)
        End If

        Throw New ArgumentException("object is not a Temperature")
    End Function

    Public Overloads Function ToString(ByVal format As String, ByVal provider As IFormatProvider) As String _
        Implements IFormattable.ToString

        If Not (format Is Nothing) Then
            If format.Equals("F") Then
                Return [String].Format("{0}'F", Me.Value.ToString())
            End If
            If format.Equals("C") Then
                Return [String].Format("{0}'C", Me.Celsius.ToString())
            End If
        End If

        Return m_value.ToString(format, provider)
    End Function

    ' Parses the temperature from a string in form
    ' [ws][sign]digits['F|'C][ws]
    Public Shared Function Parse(ByVal s As String, ByVal styles As NumberStyles, ByVal provider As IFormatProvider) As Temperature
        Dim temp As New Temperature()

        If s.TrimEnd().EndsWith("'F") Then
            temp.Value = Double.Parse(s.Remove(s.LastIndexOf("'"c), 2), styles, provider)
        Else
            If s.TrimEnd().EndsWith("'C") Then
                temp.Celsius = Double.Parse(s.Remove(s.LastIndexOf("'"c), 2), styles, provider)
            Else
                temp.Value = Double.Parse(s, styles, provider)
            End If
        End If
        Return temp
    End Function

    ' The value holder
    Protected m_value As Double

    Public Property Value() As Double
        Get
            Return m_value
        End Get
        Set(ByVal Value As Double)
            m_value = Value
        End Set
    End Property

    Public Property Celsius() As Double
        Get
            Return (m_value - 32) / 1.8
        End Get
        Set(ByVal Value As Double)
            m_value = Value * 1.8 + 32
        End Set
    End Property
End Class