小数点数の量子化

掲示板に投稿したもの

X[0.0 〜 2.0]の値を取る小数点数をY[0 .. 31]の整数型に量子化することを考える。普通に考えるとこうなる。

  1. X が 2.0 を含まない場合
    1. Y = trunc(X * 32 / 2.0)
  2. X が 2.0 を含む場合(Z = X * 31 / 2.0 と置く)
    1. Y = trunc(Z)
    2. Y = trunc(Z + 0.5) *1

X が 2.0 を含まない場合は全く問題ない。しかしそうで無い場合は2-2を使っても

  • 0.0 <= Z < 0.5 → Y = 0
  • 0.5 <= Z < 1.5 → Y = 1
  • 30.5 <= Z <= 31.0 → Y = 31 *2

つまりY=0, 31の場合だけ、とれるZの範囲が半分になってしまう。この場合の量子化を正確に行うのは少し難しい。

改良案1)

  • Z = X * (32 - delta) / 2.0 (deltaは適度に小さい数。Xが整数値ならdelta = 1)
  • Y = trunc(Z)

としてYを計算する方法がある。つまり 0.0 <= X <= 2.0 の値を 0.0 <= Z <= 31.99999... に写像してしまえばいい。

改良案2)
X = 2.0 や Y = 32 の値だけを特別扱いする。つまり、

  • Y = trunc(X * 32 / 2.0)
  • if (Y == 32) Y = 31 or Y -= (Y >= 32) or Y -= (Y >> 5)

としてしまう方法もある。

*1: 1だとXがきっかり2.0のときしかYが31にならないので少し改良

*2: Zの最大値は31のため

データと手続き

パソコンではなく、機械一般を考えてみます。普通はなんらかのデータを与えてなんらかのデータを返す何かを想像するのでは無いでしょうか。例えば、切符の発券機はお金と値段や購入数などを入力すると切符を出してくれます。また、飲み物の自動販売機はお金をいれると、その対価として飲み物を提供してくれますね。私たちが日頃から慣れ親しんでいる「機械」とはそういうものなのです。

しかし、パソコンやマイコンなどのいわゆる「プログラマブル」な機械では、その考え方を一歩進めて「データとそのデータを扱うための手続き」を与えることにしました。レジスタといわれるデータを一時的に保存するための記憶領域と、命令セットと呼ばれるそのデータを加工するためのいくつかの原始的な命令を定義したのです。そうすると、あら不思議、マイコンは発券機にも自動販売機にも電子ジャーにも使われることとなりました。パソコンもGPUも基本的にはそういった進化の過程を踏んでいます。

ではその、より原始的な命令を集めた命令セットはいったい誰がどうやって決めたのでしょうか?  実はこれは大変難しい問題で、PC系でよく使われるIntelのCPUでさえ未だに完全には固定されていません。新規命令が追加され続けているのです。ただし、一般的な話をすると、PCは基本的には数学を基盤にしているため、数学的な命令(加算や乗算など)が命令セットに含まれることが多いです。もちろん数学とは関係ない、全く新しい体系を元に命令セットを作ってもなんら問題はありません。

プログラミングの勝手な解釈

最近よく考えるのですが、プログラミングというのは極論すれば
・人間の意図(かっちょいいゲームが作りたいだとか、知り合いだけが見られる日記サイトを作りたいとか)
から
・コンピュータが解釈できるような形(アセンブラなど)
への変換作業だと簡潔に記述できるものだと思います。

一言にプログラマといってもJava使いだったりコボラーだったりといろいろありますが、本質的には「人間の意図→機械語」の長大な変換のなかのどの部分を担当しているのかといった違いしか無いのです。

c++でプロパティ

では早速
2 コンパイラの独自拡張に頼る
を実装してみます。なんで最初に2かというと、こちらの方が簡単だからです(^^;

vc6 では以下のプロパティ構文をサポートしています。

int get_Size() { return m_size; }
void set_Size(int value) { m_size = value; }
__declspec(property(put = set_Size, get = get_Size)) int Size;

また、ManagedC++では

__property int get_Size() { return m_size; }
__property void set_Size(int value) { m_size = value; }

C++/CLIでは、

property Size {
  int get() { return m_size; }
  void set(int value) { m_size = value; }
}

のような記述ができます。

基本的にはBorland系のコンパイラでも似たようなことができるのですが、肝心のgccではできません。移植性を考慮してマクロを使ったとしてもwin系コンパイラ限定のノウハウとなります。残念ながら移植性を考慮した場合は使えなさそうです。

c++でプロパティ

このところC#一辺倒でc++など忘れかけていたシーラカンスでありますが、最近またc++をいじり始めました。

よく言われていることではありますが、c++に対してC#の優れているところは、Visuralホニャララなrad環境との親和性にあると思っています。メソッド名やクラス名を書いている途中で、勝手にインテリセンスが働くのは何とも嬉しい限りです。また、リファクタリング機能がexpress版に標準装備というのもホゲホゲプログラマな自分にとっては嬉しいことです。

まあ、だから何だって感じなんですが(w

で、今回はC#で他に便利だと感じた機能 - プロパティを何とかc++で実現できないかと考えてみました。プロパティというのは見かけ上のフィールドアクセスをメソッドコールに置き換える機能です。使い方としては、例えば仕様変更が頻繁なプロトタイピング時はフィールドをpublic属性にしておき、仕様が安定してきたらそれをprivateフィールド & プロパティに置き換える、なんてことができます。またメソッド呼び出しを行えるので、値の範囲を厳密に絞るなんてこともできます。最初からsetter/getterを書いてもいいのですが、それが面倒な人向けですね。逆に言えば、無くてもいい機能とも言えますが・・・

とにかく、c++でプロパティを実装することを考えてみます。やり方としては以下のような

  1. プロパティ用プロキシオブジェクトを作成する
  2. コンパイラの独自拡張に頼る

が考えられるかと思います。次回からはこれら一つ一つを実際に実装していくことにします。

円をベジェに変換しよう2ヽ(´∀`)ノ

参考ページ(http://www.tensyo.com/urame/prog/ALGO.HTM#C1)にもあるのですが、単位円をベジェに変換するためには

ベジェ曲線の方程式は
    x(t) = (1-t)^3 x_1 + 3(1-t)^2 t x_2 + 3(1-t) t^2 x_3 + t^3 x_4
    y(t) = (1-t)^3 y_1 + 3(1-t)^2 t y_2 + 3(1-t) t^2 y_3 + t^3 y_4ベジェ曲線への当て嵌め方は
   1)  端点が一致する事
   2)  中間点が一致する事
   3)  微分が t=0,t=1で一致する事

からベジェの各制御点を求める必要があります。

円上の開始角度を\theta_1終了角度を\theta_2、ベジエの各制御点を(x_1, y_1), (x_2, y_2), (x_3, y_3), (x_4, y_4)、ベジェの式をx(t), y(t)とおくと、上の条件から

  1. 端点が一致する事
    •  x_1 = \cos \theta_1, x_4 = \cos \theta_2
    •  y_1 = \sin \theta_1, y_4 = \sin \theta_2
  2. 中間点が一致すること
    •  x(\frac{1}{2}) = \frac{1}{8} \left\( x_1 + 3 x_2 + 3 x_3 + x_4 \right\) = \cos \left\( \frac{\theta_1 + \theta_2}{2} \right\)
    •  y(\frac{1}{2}) = \frac{1}{8} \left\( y_1 + 3 y_2 + 3 y_3 + y_4 \right\) = \sin \left\( \frac{\theta_1 + \theta_2}{2} \right\)
  3. 微分が t=0,t=1で一致する事
    •  \frac{-x_1 + x_2}{-y_1 + y_2} = -\tan \theta_1
    •  \frac{-x_3 + x_4}{-y_3 + y_4} = -\tan \theta_2

未知数8つで、式も8つなのでこれは解くことができます。ただ x_1, x_4, y_1, y_4は分かってるも同然なので、これをふまえ少し簡単にしたものが、以下の式です。

  •  x_2 \cos \theta_1 + y_2 \sin \theta_1 = 1
  •  x_3 \cos \theta_2 + y_3 \sin \theta_2 = 1
  •  x_2 + x_3 = \frac{8 \cos \left\( \frac{\theta_1 + \theta_2}{2} \right\) - \cos \theta_1 - \cos \theta_2}{3} = A
  •  y_2 + y_3 = \frac{8 \sin \left\( \frac{\theta_1 + \theta_2}{2} \right\) - \sin \theta_1 - \sin \theta_2}{3} = B

(A, B は定数)

そして、これをがりがりと計算していくと以下の解が得られます。
 x_2 = \frac{A \sin \theta_1 \cos \theta_2 + B \sin \theta_1 \sin \theta_2 - \sin \theta_1 - \sin \theta_2}{\sin \left\( \theta_1 - \theta_2 \right\)}
 y_2 = - \frac{A \cos \theta_1 \cos \theta_2 + B \cos \theta_1 \sin \theta_2 - \cos \theta_1 - \cos \theta_2}{\sin \left\( \theta_1 - \theta_2 \right\)}
 x_3 = - \frac{A \cos \theta_1 \sin \theta_2 + B \sin \theta_1 \sin \theta_2 - \sin \theta_1 - \sin \theta_2}{\sin \left\( \theta_1 - \theta_2 \right\)}
 y_3 = \frac{A \cos \theta_1 \cos \theta_2 + B \sin \theta_1 \cos \theta_2 - \cos \theta_1 - \cos \theta_2}{\sin \left\( \theta_1 - \theta_2 \right\)}
これでやっとベジェに変換できました。あとはこれをプログラムに直してしまえばOKです。注意として、分母にあるsinの値が0に近くなりすぎないよう常に監視する必要があります。これを怠るとプログラムの挙動がおかしくなります。というか、なったんですが(^^; また円を分割してベジェを求める場合、最初のベジェ曲線を変換行列で回転させればいちいちこんな計算をしなくてすみます。

ちなみに参考ページにある求め方はこれの特殊な場合で、\theta_1 = 0を代入すれば全く同じ解き方になります。時間のある方は試してみてはどうでしょうか?

ではでは。皆様お休みなさい。よい夢を。