
【組込みシステム編】第4回 ビット操作と浮動小数点数、その落とし穴
こんにちは、南角です。
まず前回の問題からです。
以下の2種類の関数をC言語で作ってみてください。C言語なので参照は使用不可とします。
一つ目はありふれていますが、2つの整数を入れ替える関数Myswap、プロトタイプは次の通りです。
void Myswap(適切な引数);
もう一つは3つの整数の整数値のうち、真ん中のものを戻り値として返す関数Mymidです。
プロトタイプは次の通りです。
int Mymid(int, int, int);
どちらもビット演算を用いるということになっていましたよね。
答えの一例です。
int Mymax(int x, int y) {
return (x > y) ? x : y;
}
int Mymid(int x, int y, int z) {
return MAX(x, y) ^ MAX(y, z) ^ MAX(z, x);
}
void Myswap(int* xp, int* yp) {
*xp = *xp ^ *yp;
*yp = *yp ^ *xp;
*xp = *xp ^ *yp;
return;
}
どうですか?どちらも効率はともかく面白くないですか?
解説は省略します。
例えば次のようなプログラムで確認してみてください。
int main()
{
int x = 333, y = 111, z = 200;
printf("mid = %d\n", Mymid(x, y, z));
printf("x = %d, y = %d, z = %d\n", x, y, z);
Myswap(&y, &z);
printf("x = %d, y = %d, z = %d\n", x, y, z);
printf("mid = %d\n", Mymid(x, y, z));
return 0;
}
今回はビット関連でまとめてみましょう。
C多少プログラムに慣れてきた人が犯しやすい間違いに、シフト演算があります。
例えば、
#define PORT_A *(volatile unsigned char* )0x1000
などと定義してあり、この内容を1ビット左シフトするような場合です。
ついつい PORT_A << 1; とかですませてしまう人が結構います。
間違いがわかりますか?
正しくは PORT_A <<= 1; (あるいは PORT_A = PORT_A << 1;)ですよね。
これが1の加算であれば PORT_A++; あるいは ++PORT_A;でも構いませんよね。
これがついつい PORT_A << 1; で済ませてしまう原因ではないかと、個人的には考えています。
もう一つのビット関連は浮動小数点数です。
浮動小数点ってビットの対極にあるのではないかと、思う人もいると思います。
しかし、そもそもコンピュータが扱えるのは0、1の2進数の整数だけですよね。
これで、どうやって浮動小数点数、実数を扱えるのでしょうか?
それは、浮動小数点数を指数形式で扱っているからです。
具体的には2進数のある実数は、次の形で表せます。
1.0110010100111 * 2 ** 1001100110110111
ここで仮数部と指数部に注目してみると、それぞれは整数ですよね。
そこで仮数部と指数部をそれぞれ適当なビット数として、コンピュータで扱いやすい32ビットやや64ビットにしたものが、それぞれfloatやdoubleとなります。
(実際にはfloatやdoubleでは、正規化によって仮数部を1ビット増やしているので、33ビットと65ビットと言ってもいいのかもしれませんが…)
ここで考えなくてはならないのが、精度と符号です。
例えばfloat とlong はどちらも32ビットです。
しかしfloatの方はその32ビットの中に仮数部と指数部を持っています。
それに対してlongのほうはいわば仮数部だけを持てばいいわけです。
当然longの方が精度は高いことになります。
long a = xxx, b;
float x;
として x = (float)a;
b = (long)x;
とすると a != b となる場合があるということです。
つまり浮動集点数とは、同じビット数の整数型よりも大きな範囲の数を、ある程度の精度で記憶できる数ということになります。
これがdoubleであれば、仮数部も32ビット以上割り当てられているので
long a = xxx, b;
double x;
として x = (double)a;
b = (long)x;
とした場合、常に a == b
となります。
しかし、long long は64ビット整数なので float と long と同じことが起こりえます。
つまり何が言いたいかというと、現状は整数はlongで浮動小数点数はdoubleで扱っているシステムにおいて、将来精度を上げるために、整数の桁数を上げ、整数のみlong long とした場合に、精度関係の何らかの新たな不具合が発生する可能性があるということです。
これは、関連しそうな人は心の片隅にでも置いておいてください。
もう一つ、浮動所数点数間の差異の問題があります。
浮動小数点数の形式もIEEEで決められていますが、float や double 以外にも拡張double というものがあります。
そしてx86系のCPU(のFPU)は doubleの計算も内部的には10バイトの拡張doubleで演算しています。
ここでも少し問題になったことがあります。
VxWorksを使用していた工業用ロボットがありましたが、それが使用していたCPUはdoubleの演算を8バイトで行っていました。
そしてそのロボットのシミュレーターをPC上でも動かしていましたが、動きが異なる場合がありました。
原因は両者のdoubleの精度の違いです。PCはdoubleの計算を内部的には10バイトで行っていました。その結果、両方の動き(この場合は本当に動き、ロボットの動作)が異なる場合がありました。
この場合は、しょうがないので、最終確認は実機で行うことにしました。
浮動小数点数に関しては、まだround や 符号、 メモリ上の形式などの話などが残っていますので、次回も続けたいと思います。
では、また次回。
組込みシステム編 第4回 おわり
■著者プロフィール
南角 茂樹(なんかく しげき)
大阪電気通信大学 総合情報学部 メディアコンピュータシステム学科 准教授、同大学院総合情報学研究科(コンピュータサイエンス専攻)、エイシップ・ソリューションズ株式会社 研究顧問。
慶應義塾大学工学部数理工学科卒業後、大手電機会社を経て現職。組み込みシステムおよびリアルタイムOSを専門とし、著書、解説記事、発表・講演、登録特許等多数。
ページの先頭へ戻る »

