ホーム » スタッフ » 斉藤徹 » 講義録 » プログラム応用 » 数値の範囲とトラブル事例

2015年4月
« 3月   5月 »
 1234
567891011
12131415161718
19202122232425
2627282930  

最近の投稿(電子情報)

アーカイブ

カテゴリー

数値の範囲とトラブル事例

先日の数値の範囲の説明で、浮動小数点型(float,double)などについても説明を行う。

16bitコンピュータの時代…

簡単な桁あふれの事例として、古いコンピュータ16bitの時代の事例。

// int は16bitとする。
// パソコン画面上の2点の距離を計算したい。
int x1 , y1 ;
int x2 , y2 ;
int r = (int) sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) ) ;

このプログラムは、負の値の平方根を求めた…というエラーメッセージで止まってしまう。

これは、座標が200ドット離れていれば、2乗の和を求めた段階で、40000となるが、 16bit 符号付き整数であれば、最大数が32767 なので桁あふれして、負の数として扱われている。

2038年問題

次に、20XX年問題の説明。 2000年問題とは、古いプログラムで西暦末尾2桁で年を扱っていたプログラムが誤動作を 起こす可能性の問題。

同じように数値の範囲が原因となるものとして、2038年問題を紹介する。 OS unix では、時間を1970年からの経過秒数で表す。 この時、32bit(符号付きint型)であれば、2^31-1が扱える最大数であり、 (2^31-1) / (3600*24*365.22)を求めると、約68年となる。 つまり、32bit int であった場合は、2038年以降は桁あふれで時間を正しく扱えなくなる可能性がある。

しかし、計算の途中の段階で桁あふれして動かない事例はある。 以下の事例は、2004年に実際に発生したトラブルと同様の簡単なコードである。

// intは32bitとする
// 2つの時間の中央時間を計算するコード
int t1 , t2 ;
int tm = (t1 + t2) / 2 ;

32bitの時間情報では、2004年以降は、 t1,t2の上位ビットが01XX……となっているため、 t1+t2 を計算すると最上位ビット(符号ビット)が1となり この後の計算は、負の値として扱われた。

上記のコードは、次のように記述するか、64bit化されたtime_t型で 記述すべきである。

// 32bitでも動く
int tm = t1 + (t2 - t1) / 2 ;
// 64bitのtime_t型
time_t t1 , t2 ;
time_t tm = (t1 + t2) / 2 ; // △
time_t tm = t1 + difftime( t2 , t1 ) / 2 ; // ◎

248日問題

丁度ニュースで、「ボーイング787が248日以上連続稼働すると、電源停止で制御不能となる」というネタが 紹介されている。点検などがあるから、実際はあり得ない状況だとは思えるが、 これもint型の桁あふれが原因らしい。 あり得ないとはいえ、フライバイワイヤ(操縦桿を電気信号で制御)の最新機は、電源が落ちれば即墜落。 これは、10msec単位で、OSの経過時間をint型(32bit)で制御しているのが 原因らしい。10msec*(2^31)=248日で桁あふれが発生して、電源稼働経過時間を誤認するらしい。 これと同様の事象は、Windows Xpなどが出た頃にも発生している。 787ほど深刻な墜落はないにしても、サーバ機なら248日稼働ぐらいはよくある話。

浮動小数点型と演算順位の問題

float型やdouble型は、浮動小数点で実数の小さな値から大きな値まで扱える。


しかしながら、プログラム中の式の順序によっては、注意が必要となる。

int a[ 5 ] = { 10 , 10 , 10 , 10 , 11 } ;
int s = 0 , size = 5 ;
for( int i = 0 ; i < size ; i++ )
s += a[ i ] ;
printf( "%lf¥n" , s / size ) ;
// × %lf はdouble型、s/size はint型で異常な値が表示される。
printf( "%lf¥n" , (double)( s / size ) ) ;
// × s/sizeはint型で、小数点以下が求まらない。
printf( "%lf¥n" , (double)s / (double)size ) ;
// ◎
#define PI 3.141592
double x ;
for( x = 0.0 ; x < 2*PI ; x += (1/6) * PI ) {
printf( "%lf %lf¥n" , x , sin( x ) ) ;
}

このプログラムは、1/6が整数型で行われるため(つまり0とPIを掛ける)、 xの値が変化しない。 これは、"x += (1.0/6.0) * PI" と書かなければならない。