* 固定小数点演算
-(by [[K]], 2013.08.08)
** (0) はじめに
-OSECPUは現状では整数演算しかサポートしていないので、小数を扱うのが苦手です。しかし整数演算だけで小数を簡単に表現できる「固定小数点演算」という手法があるので、それを紹介したいと思います。
** (1) 基本
-表したい本当の数値をf、その整数表現をiとすると、
i = f * 65536
-としてiをfの代わりに使うのが固定小数点演算です。この65536は小数部が16ビットの場合の例です。
** (2) 整数部と小数部の分離
-iの整数部aは i >= 0 のとき、 a = i >> 16; で求められます。
-iの小数部bは i >= 0 のとき、 b = i & 0xffff; で求められます。
-負の場合はこの式では分離できないのでいったん正にします。
-これを利用すると、こんなふうに数値を表示できます。
i = 0x3243f; // (例)円周率: 3.141592を65536倍したもの.
if (i >= 0) {
a = i >> 16;
b = i & 0xffff;
junkApi_putStringDec('i=\1.', a, 5, 1);
} else {
i *= -1;
a = i >> 16;
b = i & 0xffff;
i *= -1;
junkApi_putStringDec('i=-\1.', a, 5, 1);
}
b *= 10000;
b >>= 16;
junkApi_putStringDec('\1', b, 4, 2);
-小数部は5桁表示にしたいところですが、そうするとbを10万倍しなければならず、65535(=bの最大値)の10万倍は32ビット符号付き整数では表せないので、処理が複雑になってしまいます。
-まあ小数部はいろいろと誤差がたまるところでもあるので、4桁くらいでちょうどいいのかもしれません。
** (3) 四則演算
-加算: i2 = i0 + i1
-減算: i2 = i0 - i1
-乗算: i2 = (i0 * i1) >> 16
-除算: i2 = (i0 << 16) / i1
-基本的にはこの式でできるのですが、乗算と除算は一時的に32ビットの範囲を超えてしまうのでよくありません。ということで工夫します。
-乗算:
sign = 1;
tmp = i0 ^ i1; if (tmp < 0) { sign = -1; } // まず符号を先に計算してしまう.
tmp = i0; if (tmp < 0) { tmp *= -1; } a0 = tmp >> 16; b0 = tmp & 0xffff;
tmp = i1; if (tmp < 0) { tmp *= -1; } a1 = tmp >> 16; b1 = tmp & 0xffff;
tmp = b0 * b1; tmp >>= 16; i2 = tmp;
tmp = b0 * b1; tmp >>= 16; i2 = tmp & 0xffff;
tmp = a0 * b1; i2 += tmp;
tmp = b0 * a1; i2 += tmp;
tmp = a1 * a1; tmp <<= 16; i2 += tmp;
i2 *= sign;
-除算:
sign = 1;
tmp = i0 ^ i1; if (tmp < 0) { sign = -1; } // まず符号を先に計算してしまう.
tmp0 = i0; if (tmp0 < 0) { tmp0 *= -1; }
tmp1 = i1; if (tmp1 < 0) { tmp1 *= -1; }
i2 = tmp0 / tmp1; i2 <<= 16;
tmp = tmp0 % tmp1; tmp <<= 16;
tmp /= tmp1; i2 += tmp;
i2 *= sign;
** (4) より高級な演算
-四則演算ができれば、テイラー展開などを使って三角関数や対数や指数関数を求めることができます。ここから先は固定小数点演算の固有の問題はあまりありません(精度の問題はあるけど)。
* こめんと欄
#comment