演算精度
===============
## 浮動小数点
このページでは,浮動小数点による演算の精度について学習します.
コンピュータ内部では,小数点以下を含む数値を処理するために,一般的に浮動小数点型や浮動小数点演算を行います.
JavaScriptでは,整数型や小数型を意識することは(あまり)有りませんが,内部的にこのような形で処理されます.
浮動小数点型というのは,m☓2^eで数値を表します.
また,そのbit数で型が分かれています.
IEEE754で定められている浮動小数点型とビット数を以下に示します.
ここで,仮数部のbit数というのはmの部分に使えるbit数で,指数部のbit数というのはeの部分に使えるbit数です.
JavaScriptの場合は,特に指定しない限り倍精度が使用されます.
型名 | 符号bit数 | 指数部bit数 | 仮数部bit数 | 総bit数
-- | --: | --: | --: | --:
半精度 | 1 | 5 | 10 | 16
単精度 | 1 | 8 | 23 | 32
倍精度 | 1 | 11 | 52 | 64
4倍精度 | 1 | 15 | 112 | 128
## 浮動小数点と0.1
10進数では1/3を計算すると0.33333333...といつまで経っても割り切れない数(循環小数)になってしまいます.
これと同じように2進数では1/3や1/5が循環小数になります.
また,1/5が循環小数になるということは,1/10も循環小数になります.
人間にとって分かりやすいと考えられている1/10は,コンピュータにとってはものすごく半端な数となってしまいます.
まずは0.1を,小数点以下40桁まで表示してみましょう.
0.1の後,途中までは0が並んでいますが,小数点以下17桁辺りから妙な数字が並んでいると思います.
コンピュータ内部では1/10が循環小数であることと,演算に使えるbit数が有限であることから,微小な誤差(丸め誤差)が生じてしまいます.
```javascript runnable console editable
// 0.1を小数点以下40桁まで表示する
var x = 0.1;
console.log( "何も指定せずに表示:" + x );
console.log( "小数点以下40桁まで表示:" + x.toFixed(40) );
```
## 微小な誤差を持つ数値の積和(1)
次に,初心者がよく陥りがちな演算の例として,0.0001を順次足していく例を示します.
例えば10000回足すと1.0になることを期待してしまいますし,100000000回足すと1000になることを期待してしまいます.
しかし,微小な誤差が積もり積もると大きな誤差になってしまいます.
このような場合は順次足していくのではなく,掛け算を使用することが望ましいです.
これらの計算結果を実際に見てみましょう.
```javascript runnable console editable
// 繰り返し足し算した結果と掛け算の結果の比較
var x = 0.00001;
var y1 = 100000;
var y2 = 100000000;
var sum = 0.0;
console.log( "初期値:" + x.toFixed(40) );
// 100000回
console.log( "\n100000回の積和");
console.log( y1 + " ☓ " + x + " = " + (x * y1).toFixed(40) );
for( var z=0; z<100000; z++ ) sum += x;
console.log( "繰り返し足した結果: " + sum.toFixed(40) );
sum = 0.0;
// 100000000回
console.log( "\n100000000回の積和");
console.log( y2 + " ☓ " + x + " = " + (x * y2).toFixed(40) );
for( var z=0; z<100000000; z++ ) sum += x;
console.log( "繰り返し足した結果: " + sum.toFixed(40) );
```
## 微小な誤差を持つ数値の積和(2)
積和で誤差が出る別の例を示します.
こちらも初心者が陥りやすいのですが,微小な数値を足していく処理を作り,その繰り返し条件が「0.0
になるまで繰り返す」としてしまう場合です.
誤差によって0.0にはならないため,そのような条件でループすると無限に繰り返します.
気をつけてください.
ここでは,-1000に順次0.0001を加えていき,10000000回でちょうど0.0になるはずのところの演算結果を見てみましょう.
```javascript runnable console editable
// 繰り返し足し算した結果と掛け算の結果の比較
var x = 0.0001;
var sum = -1000.0;
console.log( "初期値 x = " + x.toFixed(40) );
console.log( "初期値 sum = " + sum.toFixed(40) );
console.log( "\n10000000回の積和");
for( var z=0; z<10000000; z++ ) sum += x;
console.log( "繰り返し足した結果: " + sum.toFixed(40) );
console.log( "通常目にする表記: " + sum );
```
## 情報落ち(1)
浮動小数点の演算では,扱える数値の範囲が広いので忘れがちですが,仮数部のbit数を超えて記憶することはできません.
ここでは,大きな数と小さな数の加算結果を見てみましょう.
大きな数の例として1.0☓10^10,小さな数の例として1.0☓10^-10を足してみます.
表示の際のアンダーバー(_)は,桁揃えのために入れているので無視してください.
```javascript runnable editable console
// 要素番号を指定して代入する例
var x = 1e10;
var y = 1e-10;
console.log( x.toFixed(20) );
console.log( "__________" + y.toFixed(20) );
console.log( (x + y).toFixed(20) );
```
## 情報落ち(2)
上の例では小数点以下の小さな数値を足してみましたが,今度は小数点以下を含まない例を示します.
具体的には,倍精度型の仮数部は52bitなので,53bit以降の桁は何らかの処理が施されてしまうことを示します.
大きな数の例として2^53,小さな数の例として1を足してみます.
2^53は仮数部52bitを使い切ってしまいますが,そこに1を加えると53bit目に相当するので,無視されてしまいます.
```javascript runnable editable console
// 要素番号を指定して代入する例
var x = Math.pow(2.0, 53.0);
var y = 1.0;
console.log( x.toFixed(20) );
console.log( "_______________" + y.toFixed(20) );
console.log( (x + y).toFixed(20) );
```