C# で総和計算の情報落ちを見る

級数の総和を計算することで、 C# で情報落ちを試します。

情報落ち

loss of trailing digits

情報落ちとは、コンピュータで絶対値の大きさが極端に異なる数字を足したり引いたりしたときに、小さい値の情報が無視されてしまう現象。また、そのような現象によって起きる計算の誤差。

情報落ちを回避するには、計算の順序を考える必要があります。
浮動小数点数の計算上、加算・減算では小さな値が大きな数に合わせて表現しなおされますが、このときに小さな値の一部か全部が失われてしまうことがあります。
これを回避するためには、小さい値同士を加えるように処理を変えるという方法があります。

級数の総和計算では比較的わかりやすく結果がわかるため、以下の左辺の計算を、カウントアップ(つまり数が減る方向)と、カウントダウン(つまり数が増える方向)でそれぞれ実行します。これらは手で解析的に計算してみればすべて同じ値( nn+1\frac{n}{n+1} )となります。

i=1n1i(i+i)=i=1n(1i1i+1)=(1112)+(1213)++(1n11n)+(1n1n+1)=11n+1=nn+1\begin{aligned} \sum_{i=1}^{n}\frac{1}{i\left(i+i\right)} & =\sum_{i=1}^{n}\left(\frac{1}{i}-\frac{1}{i+1}\right)\\ & =\left(\frac{1}{1}-\frac{1}{2}\right)+\left(\frac{1}{2}-\frac{1}{3}\right)+\cdots+\left(\frac{1}{n-1}-\frac{1}{n}\right)+\left(\frac{1}{n}-\frac{1}{n+1}\right)\\ & =1-\frac{1}{n+1}\\ & =\frac{n}{n+1} \end{aligned}

今回は floatdouble についてみてみます。 decimal では誤差の結果は面白くなさそうなので…。ソースコード等はこちらから

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static void FloatCheckLoss()
{
const int Max = 1000000; // 10^6

float countUpSum = 0.0f;
for (int i = 1; i <= Max; i++)
{
countUpSum += 1.0f / i / (i + 1);
}

float countDownSum = 0.0f;
for (int i = Max; i > 0; i--)
{
countDownSum += 1.0f / i / (i + 1);
}

Console.WriteLine($"count up: {countUpSum:F8}");
Console.WriteLine($"count down:{countDownSum:F8}");
Console.WriteLine($"solution: {(float)Max / (Max + 1):F8}");
}

static void DoubleCheckLoss()
{
const int Max = 1000000000; // 10^9

double countUpSum = 0.0;
for (int i = 1; i <= Max; i++)
{
countUpSum += 1.0 / i / (i + 1);
}

double countDownSum = 0.0;
for (int i = Max; i > 0; i--)
{
countDownSum += 1.0 / i / (i + 1);
}

Console.WriteLine($"count up: {countUpSum:F16}");
Console.WriteLine($"count down:{countDownSum:F16}");
Console.WriteLine($"solution: {(double)Max / (Max + 1):F16}");
}

計算結果は次の通りです。

1
2
3
4
5
6
7
count up:  0.99985270
count down:0.99999900
solution: 0.99999900

count up: 0.9999999936264040
count down:0.9999999990000000
solution: 0.9999999990000000

カウントアップ(つまり数が減る方向)では、解析的な解とは違う値となってしまっています。今回のような計算では式をそのままプログラムにすると誤差が出てしまう場合もあるため、注意が必要です。