74795


下記画像のように計算すると結果に誤差が発生します。  
これは倍精度浮動小数誤差が発生している結果という認識です。

[![画像の説明をここに入力](https://i.stack.imgur.com/1AMNW.png)](https://i.stack.imgur.com/1AMNW.png)

  1. しかし、 **22.15** ではなく、 **22.14** では誤差が発生しません。

[![画像の説明をここに入力](https://i.stack.imgur.com/8c6Mq.png)](https://i.stack.imgur.com/8c6Mq.png)

  2. さらに **2400** * 22.15でも誤差が発生しません。

[![画像の説明をここに入力](https://i.stack.imgur.com/8x0J8.png)](https://i.stack.imgur.com/8x0J8.png)

### 質問

なぜ1と2は誤差が発生していないのでしょうか？


CC BY-SA 4.0
2021-03-20T14:33:40.360
74791
2021-03-21T06:15:02.410
2021-03-21T06:15:02.410
3060
12388
question
c#
double 同士の演算結果が異なるのはなぜでしょうか？


結論だけ簡単にいうと、 **たまたま** です。

一部の十進計算を行う電卓が1÷3×3の結果を1と表示する(0.99999999ではなく)のと似ていると言えるでしょう。

* * *

2の例をもとに考えると、`2400`、`22.15`という定数値はコンパイラがdouble型に変換した時点で以下のような内部表現になっています。

[code]

    2400.0 = (2)1.0010110000000000000000000000000000000000000000000000 x 2^11
    22.15  = (2)1.0110001001100110011001100110011001100110011001100110 x 2^4
    
[/code]

(`(2)`は仮数部を二進表示していることを表します。`22.15`は二進法では無限小数になるのですが、doubleの有効数字仮数部52+1ビットで打ち切られてしまっています。)

仮数部のみの掛け算を行うと、結果はこんな感じ。

[code]

    1.10011111010011111111111111111111111111111111111111111000100000000000000000000000000000000000000000000000
                                                          ^
    
[/code]

double型の仮数部は二進で52ビットしかありませんので、`^`の位置で丸め(いわゆる四捨五入、二進だから零捨一入というべきか)が行われます。

[code]

    1.1001111101010000000000000000000000000000000000000000
    
[/code]

この結果得られた、`(2)1.1001111101010000000000000000000000000000000000000000 x
2^15`は、たまたまぴったりと`53160.0`を表す値になっているわけです。

例1の場合は、それぞれの値はこんな感じ。

[code]

    2600.0 = (2)1.0100010100000000000000000000000000000000000000000000 x 2^11
    22.14  = (2)1.0110001000111101011100001010001111010111000010100100 x 2^4
    1.11000001101110000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000
                                                          ^
    57564.0 = (2)1.1100000110111000000000000000000000000000000000000000 x 2^15
    
[/code]

* * *

元の数値も演算結果もdoubleで表現できる整数値の範囲に入る場合や、小数部が`0.125`なんかの2の負冪になっている場合など、「確実に十進計算と同じ結果が得られる」と言えるような値の組合せもありますが、今回のような場合は、「たまたま」だと思って置かれた方が良いでしょう。


CC BY-SA 4.0
2021-03-20T22:46:12.467
74793
2021-03-20T23:22:16.613
2021-03-20T23:22:16.613
13972
13972
74791
answer

[code]

    var value1 = 2600 * 22.15d;
    var value2 = 2600 * 22.14d;
    var value3 = 2400 * 22.15d;
    
[/code]

というコードは（デバッグビルドであろうと）コンパイル時に定数式が計算されていて

[code]

    var value1 = 57589.999999999993;
    var value2 = 57564.;
    var value3 = 53160.;
    
[/code]

というコードが生成されています。コンパイラーが誤差を埋め込んでしまった形です。

* * *

[`BitConverter.DoubleToInt64Bits`](https://docs.microsoft.com/en-
us/dotnet/api/system.bitconverter.doubletoint64bits?view=net-5.0)を使用すると`double`の値がどのようなビットパターンで格納されているかを見ることができます。また、`String.Format`等では表示する精度を指定できます。

[code]

    using System;
    static void Print(double x) =>
        Console.WriteLine("{0:X16} / {1:F10} / {1:F11} / {1:F30}", BitConverter.DoubleToInt64Bits(x), x);
    Print(2600 * 22.15d);
    Print(2600 * 22.14d);
    Print(2400 * 22.15d);
    
[/code]

を実行すると

[code]

    40EC1EBFFFFFFFFF / 57590.0000000000 / 57589.99999999999 / 57589.999999999992724042385816574097
    40EC1B8000000000 / 57564.0000000000 / 57564.00000000000 / 57564.000000000000000000000000000000
    40E9F50000000000 / 53160.0000000000 / 53160.00000000000 / 53160.000000000000000000000000000000
    
[/code]

となっていて、表示する桁数によって切り上げられるか切り下げられるかの違いが生じます。


CC BY-SA 4.0
2021-03-20T23:12:37.433
74795
2021-03-20T23:12:37.433
4236
74791
answer