Entry

プログラミングメモ - 固定小数点数におけるシフト演算の扱いについて

2011年04月29日

しばらく巷の固定小数点数のコードを読んでるんですけれど,なんだか分からないところがあるのでメモ。

固定小数点数は,浮動小数点数と違って,小数点の位置を固定します。通常は2の冪乗でゲタを履かせる。FPU なしの整数演算だけで計算できる(したがってかなり速い)一方,浮動小数点数と比較して表現できる数の範囲が狭いのが特徴です。固定小数点数は,某所で「あまり使われない」と書いてあったけれども,ゲームを作ってるところでは当たり前のように使ってるし,画像処理や CAD 界隈でもよく使われます。組み込みでも,FPU がないターゲットやリアルタイムなシステムなんかでは固定小数点数が使われる。

で,分からないところなんだけれども,巷のコードを見ていると,ゲタを履かせたり脱がせたり(と言うのか?)するのに,シフト演算を使っているんだけれども(割と有名な OSS のプログラムもそう),こゆ場合はどうなるんだろう(ドット(".")は小数点の位置,簡単のために16bitとします)。

0xff.ff

これ,2の補数表現で符号付きの固定小数点数表現だとすると,-0.00390625 になります(0 より 2-8 少ない数)。一方,内部表現では -1 の整数です。

で,ですね。内部表現の -1 から,本来の数である -0.00390625 を取り出したいわけなんですけれど,ゲタを脱がせる(?)ことになるので,この場合は 28(= 256)で割ってやれば,元の数が出るはずです。ここで示す必要もなく,割ればちゃんと -0.00390625 が出ます。C99 より前では処理系依存だけれども,整数で取り出すときは割れば大体 0 になるし,処理系依存が気持ち悪いなら,絶対値を取って厳密に切り下げ/切捨てを制御することで帳尻が合わせられます。あたしはそうするもんだとばかり思ってました。

ただ,これ,巷のソースではシフト演算でやってるんですね。符号付きの場合,論理シフトだと符号がめちゃくちゃになっちゃうからダメなのはいいとして,算術シフトだとしても困ってしまう。というのも,算術シフトだと右シフトしても 0xffff のまま変わらないからです。算術シフトでは符号ビットが最上位ビットに補完されるので,何ビットシフトしても 0xffff のまま。おそらくこれ,負数の整数除算は0方向に切り捨てられるけど(C は C99 から。それ以前は処理系依存。C++ は未調査),2の補数表現による負数の右算術シフトは(結果的に)切り下げに相当する(結果がマイナス方向に倒れる)もんだから,結果が異なってしまうのだと思う。

ま,これはこれで,「そういう仕様です」と言い切れなくもないとは思うんだけれども,正数の場合は切り捨てで,負数の場合は切り下げになるってのは,固定小数点数の仕様としてどうなんだろう,とも思う。

もうひとつ。あたしが見たソースは,固定小数点数クラスがテンプレートになっている C++ のコードで,内部の形式をカスタマイズできるようになっているものでした。内部表現に int も std::uint64_t も使えるし,signed / unsigned の対応も,それぞれ符号あり/符号なしの固定小数点数になる点で帳尻が合っています。これはこれで便利です。しかし,ご存知の通り C ではシフト演算子(<<, >>)が論理シフトなのか算術シフトなのかについて,処理系依存としているので,これは多分ジェネリックに見えてジェネリックじゃないコードなんじゃないかと思う。処理系は元から決め打ちしてるのか,それとも内部の型(テンプレート引数)を決め打ちしているのか……よく分からんけれど,多分これだと動かない処理系が出るはず。

ま,ただ思っただけ。思い違いかもしれないけど。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN