コンピュータで数値を扱う場合、誤差のことを考えておく必要がある場合があります。
例えば、char型変数は「-128〜127」までの数値しか扱えません。
unsigned char型はマイナスが表現できない代わりに「0〜255」の数値が扱えます。
しかし、両方とも「300」を代入するとおかしな値が表示されます。
表現できる最大の値を超えていますので、おかしな値になるのですが、これを「オーバーフロー」と言います。
逆に「-300」という値も表現できる最小の値を超えていますので、「アンダーフロー」が発生します。
小数を扱う場合も同じく「オーバーフロー」「アンダーフロー」が発生します。
また、「1÷3」や「1÷7」等の計算を行った場合、結果は「循環小数」になるため、途中で計算を打ち切ったりすることによる誤差が生じます。
例えば、10進数「0.1」という数値をコンピュータで扱った場合どうなるか試してみましょう。
具体的には「小数の表現4」で使ったプログラムを使って「0.1」をfloat型で表現した場合の中身を覗いてみましょう。
<sample program col021-01>
#include <stdio.h> union sample { float float_data; unsigned char char_data[4]; }; int main(void) { union sample data; int i; data.float_data = 0.1f; for (i = 3; i >= 0; i--) { printf("%02x ", data.char_data[i]); } printf ( "\n"); } |
<実行結果>
3d cc cc cd 続行するには何かキーを押してください・・・
2進数に直すと、
0011 1101 1100 1100 1100 1100 1100 1101
です。
float型の表現方法は、「小数の表現4」で説明しました。
左から、
「符号」 → 1ビット 「指数」 → 8ビット 「仮数」 → 23ビット
ですから、
「符号」 → 0 「指数」 → 01111011 「仮数」 → 10011001100110011001101
となります。
「符号」は「0」ですから、プラスです。
「指数」は「0111 1011」ですから、10進数「123」です。
「仮数」は、長いですから後で書きましょう。
float型の数値を10進数に戻すには、下の式にそれぞれの値を当てはめてみます。
「符号」(「仮数」+1) × 2 の 「指数−127」乗
「符号」と「指数」は上で計算しましたから、
+(「仮数」+1) × 2 の 「123−127」乗 ↓ +(「仮数」+1) × 2 の −4乗
では、仮数を計算してみます。
「仮数」は「0.」の部分が省略されていますので、正しくは
0.10011001100110011001101
です。
ですから、
+(0.10011001100110011001101 + 1) × 2 の −4乗 ↓ +1.10011001100110011001101 × 2 の −4乗
となります。
2の−4乗のままでは計算しづらいため、2の0乗(つまり1)という形に変化させていきます。
「小数の表現3」でやったことを2進数で行います。
+0.110011001100110011001101 × 2 の −3乗 ↓ +0.0110011001100110011001101 × 2 の −2乗
↓ +0.00110011001100110011001101 × 2 の −1乗
↓ +0.000110011001100110011001101 × 2 の 0乗
やっと2の0乗になりました。
後は、
+0.000110011001100110011001101
を10進数に直せば良いだけです。
最初に1が出てくるのは、「0.0625の位」です。
その次が「0.03125の位」ですが、電卓等を使って計算すると楽ですね。
「1」になっている「位」を全て書きます。
0.0625 0.03125 0.00390625 0.001953125 0.000244140625 0.0001220703125 0.0000152587890625 0.00000762939453125 0.00000095367431640625 0.000000476837158203125 0.000000059604644775390625 0.0000000298023223876953125 0.000000007450580596923828125
これを全て加算すると、
0.100000001490116119384765625
となります。
「0.1」を微妙に超えていますね。
実際にプログラムを書いて実行してみましょう。
<sample program col021-02>
#include <stdio.h> int main(void) { float data; data = 0.1f; printf("data = %f\n", data); } |
<実行結果>
0.100000 続行するには何かキーを押してください・・・
※float型を表示する時は「%f」を使います。
float型の変数に数値を代入する時に「0.1f」と書いてありますが、「0.1」と書くと警告が表示されます。
単純に「0.1」と書くとコンピュータ内部ではdouble型とみなされます。
double型は64ビット、float型は32ビットですから、大きいサイズから小さいサイズに代入される時に警告が出るのです。
それを回避するため、小数の末尾に「f」(float型の意)を加えました。
ちゃんと「0.1」と出ているように見えます。
しかし、上の計算結果にあるように、もっと下の桁で微妙な誤差が出ています。
double型の時と同じく表示する桁数を合わせてみましょう。
「0.100000001490116119384765625」は、全部で29桁あって小数点以下が27桁ありますから、「%29.27f」としてみます。
<sample program col021-03>
#include <stdio.h> int main(void) { float data; data = 0.1f; printf("data = %29.27f\n", data); } |
<実行結果>
0.100000001490116120000000000 続行するには何かキーを押してください・・・
0.100000001490116119384765625 0.100000001490116120000000000
途中で値が異なっていますが、ほぼ同じ数値になりました。
結局、10進数「0.1」はコンピュータ内部では、「0.1」では無いのです。
もう1つプログラムを実行してみましょう。
<sample program col021-04>
#include <stdio.h> int main(void) { float data1 = 0.1f; float data2 = 1000.0f; if (data1 == data2 - 999.9f) { printf("OK\n"); } } |
<実行結果>
続行するには何かキーを押してください・・・
data1には「0.1」、data2には「1000.0」が入っています。
data1の「0.1」とdata2の「1000.0」から「999.9」を引いた「0.1」が同じかどうか比較しています。
計算上は同じ「0.1」ですから「OK」と表示されそうですが、実行結果は何も表示されていません。
誤差を少しでも見るために表示桁数を増やしてみます。
<sample program col021-05>
#include <stdio.h> int main(void) { float data1 = 0.1f; float data2 = 1000.0f; printf("data1 = %20.16f\n", data1); printf("data2 = %20.16f\n", data2); printf("data2 - 999.9f = %20.16f\n", data2 - 999.9f); if (data1 == data2 - 999.9f) { printf("OK\n"); } } |
<実行結果>
data1 = 0.100000001490116 data2 = 1000.000000000000000 data2 - 999.9f = 0.100000000000023 続行するには何かキーを押してください・・・
どうでしょう、「同じ」ではありませんね。
小数を扱う場合、「==」を使うのは非常に危険です。
他にも色々な誤差がありますが、みなさんも調べてみてください。
ブラウザの戻るボタンで戻ってください。