実数型(float / double)
実数型は、単精度実数(float型)と、倍精度実数(double型)があり、それぞれ32bit,64bitでデータを扱う。
指数表現は、大きい値や小さい値を表現する場合に使われ、物理などで1.2345×10-4といった、仮数×基数指数で表現する方法。数学や物理では基数に10を用いるが、コンピュータの世界では基数を2とすることが多い。
単精度型(float)では、符号1bit,指数部8bit,仮数部23bitで値を覚え、数値としては、以下の値を意味する。
符号✕ 1.仮数部 ✕ 2(指数数部-127)
符号部は、正の値なら0, 負の値なら1 を用いる。
仮数部が23bitなので、有効桁(正しい桁の幅)は約7桁となる。
例えば、float型で扱える最大数は、以下のようになる。
0,1111,1111,111,1111,1111,1111,1111,1111 = 1.1111…×2128 ≒ 2129 ≒ 1038
float 型は、計算精度が低いので 通常の数値計算のプログラミングではあまり使われることはない。一方で、ゲームなどの3次元座標計算などでは、精度は必要もないことから、GPU(グラフィックス専用のプロセッサ)では float 型を使うことも多い。また、最近の機械学習のプログラミングでは、神経の動きをまねた計算(ニューラルネットワークプログラミング)が行われるが、これも精度はあまり高くなくてもいいので float 型を使うことも多く、グラフィックス用の GPU で float 型で機械学習の計算を行うことも多い。
倍精度型(double)では、符号1bit,指数部11bit,仮数部52bitで値を覚え、数値としては、以下の意味を持つ。
符号✕ 1.仮数部 ✕ 2(指数部-1023)
これらの実数で計算を行うときには、0.00000001011×210といった値の時に、仮数部に0が並んだ状態を覚えると、計算の精度が低くなるので、1.01100000000×22のように指数部の値を調整して小数点の位置を補正しながら行われる。
double型の場合、52bit=10進数16桁相当の有効桁、最大数で、1.1111…×21024≒10308
倍精度型を使えば、正しく計算できるようになるかもしれないが、実数型はただの加算でも仮数部の小数点の位置を合わせたりする処理が必要で、浮動小数点専用の計算機能を持っていないような、ワンチップコンピュータでは整数型にくらべると10倍以上遅い場合もある。
実数の注意点
C言語でプログラムを作成していて、簡単な数値計算のプログラムでも動かないと悩んだことはないだろうか?解らなくて友達のプログラムを真似したら動いたけど、なぜ自分のプログラムは動かなかったのか深く考えたことはあるだろうか?
単純な合計と平均
整数を入力し、最後に合計と平均を出力するプログラムを以下に示す。
しかし、C言語でこのプログラムを動かすと、10,10,20,-1 と入力すると、合計(sum)40,件数(cnt)3で、平均は13と表示され、13.33333 とはならない。
小数点以下も正しく表示するには、どうすればいいだろうか?
ただし、変数の型宣言を “double data,sum,cnt ;” に変更しないものとする。
// 入力値の合計と平均を求める。 #include <stdio.h> int main() { int data ; int sum = 0 ; int cnt = 0 ; for(;;) { printf( "数字を入力せよ。-1で終了¥n" ) ; scanf( "%d" , &data ) ; if ( data < 0 ) break ; cnt = cnt + 1 ; sum = sum + data ; } printf( "合計 %d¥n" , sum ) ; printf( "平均 %d¥n" , sum / cnt ) ; }
C言語では、int型のsum / int型のcnt の計算は、int 型で計算を行う(小数点以下は切り捨てられる)。このため、割り算だけ実数で行いたい場合は、以下のように書かないといけない。
printf( "平均 %lf¥n" , (double)sum / (double)cnt ) ;
// (double)式 は、sum を一時的に実数型にするための型キャスト
まずは動く例
以下のプログラムは、見れば判るけど、th を 0度〜360度まで5度刻みで変化させながら、y = sin(th) の値を表示するプログラム。
// sin の値を出力 #include <stdio.h> #include <math.h> int main() { double th , y ; for( th = 0.0 ; th <= 360.0 ; th += 5.0 ) { y = sin( th / 180.0 * 3.1415926535 ) ; printf( "%lf %lf¥n" , th , y ) ; } return 0 ; }
動かないプログラム
では、以下のプログラムはどうだろうか?
// case-1 ---- プログラムが止まらない #define PI 3.1415926535 int main() { double th , y ; // 0〜πまで100分割でsinを求める for( th = 0.0 ; th != PI ; th += PI / 100.0 ) { y = sin( th ) ; printf( "%lf %lf¥n" , th , y ) ; } return 0 ; }
// case-2 ---- y の値が全てゼロ int main() { int th ; double y ; for( th = 0 ; th <= 360 ; th += 5 ) { y = sin( th / 180 * 3.1415926535 ) ; printf( "%d %lf¥n" , th , y ) ; } return 0 ; }
どちらも、何気なく読んでいると、動かない理由が判らないと思う。そして、元のプログラムと見比べながら、case-1 では、「!=」を「<=」に書き換えたり、case-2 では、「int th ;」を「double th ;」に書き換えたら動き出す。
では何が悪かったのか…
回答編
数値と誤差
コンピュータで計算すると、計算結果はすべて正しいと勘違いをしている人も多い。ここで、改めて誤差について考える。特に、計器で測定した値であれば、測定値自体に誤差が含まれている。
こういった誤差が含まれる数字を扱う場合注意が必要である。例えば実験値を手書きで記録する場合、12.3 と 12.300 では意味が異なる。測定値であやふやな桁を丸めたのであれば、前者は 12.2500〜12.3499… の間の値であり有効数字3桁である。後者は、12.2995〜12.300499… の間の値であり、有効数字5桁である。このため、誤差が含まれる数字の加算・減算・乗算・除算では注意が必要である。
加減乗除算の場合
加減算であれば小数点の位置を揃え、誤差が含まれる桁は有効桁に含めてはいけない。
上記の計算では、0.4567の0.0567の部分は意味がないデータとなる。(情報落ち)
乗除算であれば、有効桁の少ない値と有効桁の多い値の計算では、有効桁の少ない方の誤差の影響が計算結果に出てくるため、通常は、有効桁5桁と2桁の計算であれば、乗除算結果は少ない2桁で書くべきである。
桁落ち
有効桁が大きい結果でも、減算が含まれる場合は注意が必要である。
例えば、以下のような計算では、有効桁7桁どうしでも、計算結果の有効桁は3桁となる。
このような現象は、桁落ちと呼ばれる。
演習問題(4回目)
こちらのフォルダに示す、Excel の表で、有効桁を考えてもらうための演習問題(ランダムに値が作られます)を有効数字を考えながら計算し、答えをレポートにまとめてください。例を以下に示す。
レポートは、こちらのひな型をベースに作成し(手書きノートをキャプチャした資料でもOKです)、同じフォルダに提出してください。