TrueType Instructionで遊ぶシリーズ。
初めてのTrueType命令: Windowsでは見えないフォントをつくる - にせねこメモ
フォントサイズに合わせて回転するフォントを作る(1) - にせねこメモ
PPEM・ポイントサイズを表示するフォント - にせねこメモ
TrueType Instructionを使って回転を計算したい。そうなると当然三角関数が使いたくなる。一方でTrueType Instruction Setには三角関数を計算する命令は入っていない。そのため自前で計算する必要がある。
方針
さて、自前で計算するにはどうすればいいだろうか?方法はいくつかある。
一つはMaclaurin展開(あるいはTaylor展開)によって計算するもの(たぶんこっちのが実装は楽)。
一つはCORDICというアルゴリズム(計算方法について調べてるときに教えてもらった)。
どちらも四則演算のみによって計算できる。
今回はCORDICというアルゴリズムを使ってみる。これは非負整数についてとなるようなの値を事前にテーブルとして持っておいて、その値に四則演算をしていくことで、最終的な関数を得るというものらしい*1。テーブルとして持つの上限は必要とする精度によって決める。詳しくは次のサイトを参考にされたい。
関数電卓コラム 10/02/24 関数電卓のしくみ(CORDICアルゴルズム)
F26Dot6の精度
さて、TrueType命令で使える小数フォーマットはF26Dot6(以降Q6とも書く)、すなわち整数部が26ビットで小数部が6ビットである固定小数点数である。
Q6の値は整数と同様の扱いであるが、だけ下駄がはかされていると考えるとよい。すなわち、Q6を整数として解釈した数値を64で割ると実際の数値が求まる。
例えばQ6フォーマットで表される数値を整数で表現すると、Q6の1.0は整数の64、Q6の2.0は整数の128、Q6の0.75は整数の48、などとなる。
TrueType命令において、足し算(ADD[ ])・引き算(SUB[ ])は整数に対して定義されている。これは小数点の位置を変化させないため固定小数点数に対してもそのまま使える。
一方で掛け算(MUL[ ])や割り算(DIV[ ])はF26Dot6に対して定義されていて、計算結果もF26Dot6である。これは、実際には次のような計算が行われるとのことである。
A * B: (n1 * n2) / 64 A / B: (n1 * 64) / n2
n1, n2は整数として扱われ、基本的に整数同士の掛け算あるいは割り算と同様であるが、固定小数点数なので右端(LSB)から6ビットが小数部であることを考慮して、最終的な計算結果がF26Dot6となるように、適当なところで64で割ったり64を掛けたりして桁を調整している。64で割るのは6ビット分算術右シフトすることと同等で、64掛けるのは6bit分左シフトするのと同等であるが、TrueType命令にビットシフト演算はない。
ところで、A*Bの場合は計算結果を64で割っていて、A/Bの場合はAに64を掛けている。仕様に記載があるかもしれないし、または実装に依るのかもしれないが、32bit整数型で計算していた場合、A*Bの先頭6bitは桁あふれによって、またA/BにおけるAの先頭6ビットのデータについてもデータが失われた状態になりそうな気がする。そう考えると、乗除算で実質に使えるのは26ビット程度(20Dot6、つまり-524288.~524287.程度)になるかもしれない。大きな数値を扱う必要がある場合には問題になる可能性があるかもしれない。
それにしても、フォントの制御点を表すためとはいえ、小数部が6bitというのはかなり心もとない気がする。であるから、10進数で0.01の細かさは表現できないことになる。さすがにやを計算するのにこれだと心許ない気がするので、さらに6bit分下駄をはかせて20Dot12 (Q12)相当として計算していくことにする。乗除算を計算する際には上で示したようにF26Dot6用で計算されるため、出力される数値の小数点の位置を適宜調整する必要がでてくることに気を付けないといけない。
CORDICアルゴリズムの実装
定数のストア
まず使用する定数類をStorage Areaに保存して、プログラム内から読み出すことを考えてみる。
Storage Areaへの割り付けは次のようにする。定数には6ビット分(=64)だけ下駄をはかせている。
index | value | 備考 |
---|---|---|
0 | 0x2D000 | |
1 | 0x1A90A | |
2 | 0xE094 | |
3 | 0x7200 | |
4 | 0x3938 | |
5 | 0x1CA3 | |
6 | 0x0E52 | |
7 | 0x0729 | |
8 | 0x0394 | |
9 | 0x01CA | |
10 | 0x00E5 | |
11 | 0x0072 | |
12 | 0x0039 | |
13 | 0x1A59 |
定数なので'prep'でstoreしてしまおう。
ここで、PUSHWでは1ワード(16 bit)が符号拡張されるため、すなわち1ワードの最上位ビットが符号ビットとなるため、0x8000以上の数値はそのままPUSHWを使ってスタックに積むことはできない。例えば、
(上14ビット)*2^14 + (下14ビット)
みたいに値を分割してPUSHして後で合成してやらないといけない。
'prep'
PUSHB_1 /* store dest */ 0 PUSHW_4 0x1000 /* 0x2D000 % 2^14 */ 0x000B /* 0x2D000 / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD WS PUSHB_1 /* store dest */ 1 PUSHW_4 0x290A /* 0x1A90A % 2^14 */ 0x0006 /* 0x1A90A / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD WS PUSHB_1 /* store dest */ 2 PUSHW_4 0x2094 /* 0xE094 % 2^14 */ 0x0003 /* 0xE094 / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD WS PUSHB_1 /* store dest */ 3 PUSHW_1 0x7200 WS PUSHB_1 /* store dest */ 4 PUSHW_1 0x3938 WS PUSHB_1 /* store dest */ 5 PUSHW_1 0x1CA3 WS PUSHB_1 /* store dest */ 6 PUSHW_1 0x0E52 WS PUSHB_1 /* store dest */ 7 PUSHW_1 0x0729 WS PUSHB_1 /* store dest */ 8 PUSHW_1 0x0394 WS PUSHB_1 /* store dest */ 9 PUSHW_1 0x01CA WS PUSHB_1 /* store dest */ 10 PUSHW_1 0x00E5 WS PUSHB_1 /* store dest */ 11 PUSHW_1 0x0072 WS PUSHB_1 /* store dest */ 12 PUSHW_1 0x0039 WS PUSHB_1 /* store dest */ 13 PUSHW_1 0x1A59 WS
計算の実装
さて、CORDICアルゴリズムで実際に計算をする部分の実装に入る。
入力は角度(F26Dot6)、出力は(F26Dot6)とする。
入力となる角度はの範囲にあるものとする。
Pythonで書くとこんな感じになる。
import math # constants 定数格納部分 theta = [math.atan(1.0 / (2**x)) * 180 / math.pi for x in range(13)] rsq = [2.0] for i in range(1,13): rsq.append(rsq[-1] + rsq[-1]/(2.0**(2*i))) r = list(map(math.sqrt, rsq)) # calculate sin, cos 計算部分 deg = 30 #input angle 入力の角度 [0,90] d = theta[0] # current temporary angle x = 1.0 y = 1.0 for i in range(1,13): lastx = x lasty = y if d > deg: d = d - theta[i] x = lastx + lasty/(2**i) y = lasty - lastx/(2**i) else: d = d + theta[i] x = lastx - lasty/(2**i) y = lasty + lastx/(2**i) mycos = x/r[12] #cos計算結果 mysin = y/r[12] #sin計算結果 #print("mycos:", mycos, ", mysin:", mysin) #print(" cos:", math.cos(deg/180*math.pi), ", sin:", math.sin(deg/180*math.pi))
定数格納はしたので、計算部分を実装する。
最初に入力を与えて、それに対応するを計算するような次のプログラムをつくった。
PUSHW_1 1920 /* theta=30.0 */ /* input angle */ /* ...|theta| */ PUSHW_1 4096 /* ...|theta|64.0| */ MUL /* ...|64t| */ /* 64t = 64*theta, 更に6ビット下駄を履かす */ PUSHW_2 /* ...|64t|64*1.0|64*1.0| */ 4096 4096 PUSHB_1 0 /* ...|64t|64*1.0|64*1.0|0| */ DUP /* ...|64t|64*1.0|64*1.0|0|0| */ RS /* ...|64t|64*1.0|64*1.0|0|64t0| *//*t0=theta_0=StorageArea[0]*/ PUSHB_1 64 /* ...|64t|64*1.0|64*1.0|0|64t0|1.0| */ SWAP /* ...|64t|64*1.0|64*1.0|0|1.0|64t0| */ /* ...|64t|64x=64*1.0|64y=64*1.0|i=0|2.0^i=1.0|ti=64t0| */ /* i: counter, 2.0^i: 冪関数やビットシフト演算の代替*/ /* ti: current angle */ /* L0: */ /* i = i+1, 2.0^i = 2.0^(i+1) に更新 */ ROLL /* ...|64t|64x|64y|2.0^i|64ti|i| */ PUSHB_1 1 /* ...|64t|64x|64y|2.0^i|64ti|i|1| */ ADD /* ...|64t|64x|64y|2.0^i|64ti|i+1| */ ROLL /* ...|64t|64x|64y|64ti|i+1|2.0^i| */ PUSHB_1 128 /* ...|64t|64x|64y|64ti|i+1|2.0^i|2.0| */ MUL /* ...|64t|64x|64y|64ti|i+1|2.0^(i+1)| */ ROLL /* ...|64t|64x|64y|i+1|2.0^(i+1)|64ti| */ /* if i <= 12 */ PUSHB_1 3 CINDEX /* ...|64t|64x|64y|i|2.0^i|64ti|i| */ PUSHB_1 12 /* ...|64t|64x|64y|i|2.0^i|64ti|i|12| */ LTEQ /* ...|64t|64x|64y|i|2.0^i|64ti|i<=12| */ IF /* ...|64t|64x|64y|i|2.0^i|64ti| */ /* if (i <= 12) */ PUSHB_1 5 MINDEX /* ...|64t|64y|i|2.0^i|64ti|64x| */ PUSHB_1 5 MINDEX /* ...|64t|i|2.0^i|64ti|64x|64y| */ DUP /* ...|64t|i|2.0^i|64ti|64x|64y|64y| */ PUSHB_1 5 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y|2.0^i| */ DIV /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i| */ PUSHB_1 3 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x| */ PUSHB_1 6 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x|2.0^i| */ DIV /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64x/2.0^i|64y| */ SWAP /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ /* if (ti<t) { */ PUSHB_1 5 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti| */ PUSHB_1 9 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti|64t| */ LT /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti<64t| */ IF /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ /* if ti<t */ /* x -= y/(2^i) , y+= x/(2^i)*/ ADD /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y+64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y+64x/2.0^i|64x| */ ROLL /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x|64y/2.0^i| */ SUB /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x-64y/2.0^i| */ SWAP /* ...|64t|i|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i| */ PUSHB_1 5 MINDEX /* ...|64t|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti| */ /* ti = ti+thetai */ PUSHB_1 3 CINDEX /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|i| */ RS /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|theta_i| */ ADD /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti+theta_i| */ ELSE /* if it>= t */ /* x += y/(2^i) , y-= x/(2^i)*/ SUB /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y-64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y-64x/2.0^i|64x| */ ROLL /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x|64y/2.0^i| */ ADD /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x+64y/2.0^i| */ SWAP /* ...|64t|i|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i| */ PUSHB_1 5 MINDEX /* ...|64t|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti| */ /* ti = ti-theta_i */ PUSHB_1 3 CINDEX /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|i| */ RS /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|theta_i| */ SUB /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti-theta_i| */ EIF PUSHW_1 -87 JMPR /*goto L0*/ EIF /* ...|64t|64x|64y|i|2.0^i|64ti| */ POP /* ...|64t|64x|64y|i|2.0^i| */ POP /* ...|64t|64x|64y|i| */ POP /* ...|64t|64x|64y| */ PUSHB_1 13 /* ...|64t|64x|64y|13| */ RS /* ...|64t|64x|64y|64r_12| */ /* 64r_12=StorageArea[13] */ DUP /* ...|64t|64x|64y|64r_12|64r_12| */ ROLL /* ...|64t|64x|64r_12|64r_12|64y| */ PUSHW_1 4096 /* ...|64t|64x|64r_12|64r_12|64y|64.0| */ MUL /* ...|64t|64x|64r_12|64r_12|64*64y| */ SWAP /* ...|64t|64x|64r_12|64*64y|64r_12| */ DIV /* ...|64t|64x|64r_12|64sin| */ /* 64sin = 64*y/r_12 */ ROLL /* ...|64t|64r_12|64sin|64x| */ PUSHW_1 4096 /* ...|64t|64r_12|64sin|64x|64.0| */ MUL /* ...|64t|64r_12|64sin|64*64x| */ ROLL /* ...|64t|64sin|64*64x|64r_12| */ DIV /* ...|64t|64sin|64cos| */ /* 64cos = 64*x/r_12 */ ROLL /* ...|64sin|64cos|64t| */ POP /* ...|64sin|64cos| */
やっぱStorage Area使わない方がいいよね
Storage Area使うと、他の関数や命令で使う値を潰してしまうかもしれないので、あまりよろしくない。という訳で、Storage Area使わない版もつくった。
theta_iの定数には、順番に、かつ1回しかアクセスしないため、スタックの下の方に定数を先に使う方が上になるように積んでおいて(theta_12, theta_11, ..., theta_1, theta_0)、MINDEXで若い順にスタックトップに取り出して使うようにした。
MINDEXはスタックトップから数えるので、ループでも、毎回同じ番号を指定することで定数に順番にアクセスできる。
PUSHW_1 1920 /* 30 deg */ /*入力として与える角度*/ /* stack: ...|theta| */ /* PUSH constants */ NPUSHW 10 0x0039 0x0072 0x00E5 0x01CA 0x0394 0x0729 0x0E52 0x1CA3 0x3938 0x7200 PUSHW_4 0x2094 /* 0xE094 % 2^14 */ 0x0003 /* 0xE094 / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD PUSHW_4 0x290A /* 0x1A90A % 2^14 */ 0x0006 /* 0x1A90A / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD PUSHW_4 0x1000 /* 0x2D000 % 2^14 */ 0x000B /* 0x2D000 / 2^14 */ 0x4000 /* 0x100.0 */ 0x1000 /* 0x40.0 */ MUL MUL ADD /* ...|theta|theta_12|theta_11|...|theta_0| */ PUSHB_1 14 MINDEX /* ...|64theta_12|...|64theta_0|theta| */ PUSHW_1 4096 /* ...|64theta_12|...|64theta_0|theta|64.0| */ MUL /* ...|64theta_12|...|64theta_0|64t| */ /* 64t=64*theta */ /* ...|64theta_i|64t| */ PUSHW_2 /* ...|64theta_0|64t|64*1.0|64*1.0| */ 4096 4096 PUSHB_1 0 PUSHB_1 64 /* ...|64theta_0|64t|64*1.0|64*1.0|i=0|1.0| */ PUSHB_1 6 MINDEX /* ...|64theta_1|64t|64*1.0|64*1.0|i=0|1.0|64theta_0| */ /* ...|64theta_i|64t|64x=64*1.0|64y=64*1.0|i=0|2.0^i=1.0|64ti=64theta_0| */ /*L0:*/ /* i = i+1, 2.0^i = 2.0^(i+1) に更新 */ /* ...|64t|64x|64y|i|2.0^i|64ti| */ ROLL /* ...|64t|64x|64y|2.0^i|64ti|i| */ /* i++ */ PUSHB_1 1 /* ...|64t|64x|64y|2.0^i|64ti|i|1| */ ADD /* ...|64t|64x|64y|2.0^i|64ti|i+1| */ ROLL /* ...|64t|64x|64y|64ti|i+1|2.0^i| */ PUSHB_1 128 /* ...|64t|64x|64y|64ti|i+1|2.0^i|2.0| */ MUL /* ...|64t|64x|64y|64ti|i+1|2.0^(i+1)| */ ROLL /* ...|64t|64x|64y|i+1|2.0^(i+1)|64ti| */ /* if i <= 12 */ PUSHB_1 3 CINDEX /* ...|64t|64x|64y|i|2.0^i|64ti|i| */ PUSHB_1 12 /* ...|64t|64x|64y|i|2.0^i|64ti|i|12| */ LTEQ /* ...|64t|64x|64y|i|2.0^i|64ti|i<=12| */ IF /* ...|64t|64x|64y|i|2.0^i|64ti| */ /* if (i <= 12) */ PUSHB_1 5 MINDEX /* ...|64t|64y|i|2.0^i|64ti|64x| */ PUSHB_1 5 MINDEX /* ...|64t|i|2.0^i|64ti|64x|64y| */ DUP /* ...|64t|i|2.0^i|64ti|64x|64y|64y| */ PUSHB_1 5 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y|2.0^i| */ DIV /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i| */ PUSHB_1 3 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x| */ PUSHB_1 6 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x|2.0^i| */ DIV /* ...|64t|i|2.0^i|64ti|64x|64y|64y/2.0^i|64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64x/2.0^i|64y| */ SWAP /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ /* if (ti<t) { */ PUSHB_1 5 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti| */ PUSHB_1 9 CINDEX /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti|64t| */ LT /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i|64ti<64t| */ IF /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y|64x/2.0^i| */ /* if ti<t */ /* x -= y/(2^i) , y+= x/(2^i)*/ ADD /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y+64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y+64x/2.0^i|64x| */ ROLL /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x|64y/2.0^i| */ SUB /* ...|64t|i|2.0^i|64ti|64y+64x/2.0^i|64x-64y/2.0^i| */ SWAP /* ...|64t|i|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|2.0^i|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i| */ PUSHB_1 5 MINDEX /* ...|64t|64ti|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti| */ /* ti = ti+theta_i */ PUSHB_1 7 MINDEX /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti|theta_i| */ ADD /* ...|64t|64x-64y/2.0^i|64y+64x/2.0^i|i|2.0^i|64ti+theta_i| */ ELSE /* if it>= t */ /* x += y/(2^i) , y-= x/(2^i)*/ SUB /* ...|64t|i|2.0^i|64ti|64x|64y/2.0^i|64y-64x/2.0^i| */ ROLL /* ...|64t|i|2.0^i|64ti|64y/2.0^i|64y-64x/2.0^i|64x| */ ROLL /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x|64y/2.0^i| */ ADD /* ...|64t|i|2.0^i|64ti|64y-64x/2.0^i|64x+64y/2.0^i| */ SWAP /* ...|64t|i|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|2.0^i|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i| */ PUSHB_1 5 MINDEX /* ...|64t|64ti|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i| */ PUSHB_1 5 MINDEX /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti| */ /* ti = ti-theta_i */ PUSHB_1 7 MINDEX /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti|theta_i| */ SUB /* ...|64t|64x+64y/2.0^i|64y-64x/2.0^i|i|2.0^i|64ti-theta_i| */ EIF PUSHW_1 -85 JMPR /*goto L0*/ EIF /* ...|64t|64x|64y|i|2.0^i|64ti| */ POP /* ...|64t|64x|64y|i|2.0^i| */ POP /* ...|64t|64x|64y|i| */ POP /* ...|64t|64x|64y| */ PUSHW_1 /* ...|64t|64x|64y|64r_12| */ /* 64*r12 */ 0x1A59 DUP /* ...|64t|64x|64y|64r_12|64r_12| */ ROLL /* ...|64t|64x|64r_12|64r_12|64y| */ PUSHW_1 4096 /* 64.0 */ MUL /* ...|64t|64x|64r_12|64r_12|64*64y| */ SWAP /* ...|64t|64x|64r_12|64*64y|64r_12| */ DIV /* ...|64t|64x|64r_12|64sin| */ /* 64sin = 64*y/r_12 */ ROLL /* ...|64t|64r_12|64sin|64x| */ PUSHW_1 4096 /* 64.0 */ MUL /* ...|64t|64r_12|64sin|64*64x| */ ROLL /* ...|64t|64sin|64*64x|64r_12| */ DIV /* ...|64t|64sin|64cos| */ /* 64cos = 64*x/r_12 */ ROLL /* ...|64sin|64cos|64t| */ POP /* ...|64sin|64cos| */
計算の定義域の拡大
さて、0~90度の範囲だけしか計算できないというのはよろしくないので、これ以外の範囲にも対応できるようにする。
最初に、がの範囲に入るようにする。
これは、三角関数がの周期関数であることから、自然数nについて
が成り立つことによる。つまり、が
- 360以上 → 360未満になるまで360を引く
- 0未満 → 0以上になるまで360を足す
/* stack: ...|theta(F26Dot6)| */ /* L1: */ DUP /* ...|theta|theta| */ PUSHW_1 23040 /* ...|theta|theta|360.0| */ GTEQ /* ...|theta|theta>=360.0| */ IF /* ...|theta| */ /* if (theta >= 360.0) */ PUSHW_1 23040 /* ...|theta|360.0| */ SUB /* ...|theta-360.0| */ PUSHW_1 -13 /* ...|theta-360.0|-13| */ JMPR /* ...|theta-360.0| */ /* goto L1 */ EIF /* L2: */ DUP /* ...|theta|theta| */ PUSHB_1 0 /* ...|theta|theta|0.0| */ LT /* ...|theta|theta<0.0| */ IF /* ...|theta| */ /* if (theta < 0.0) */ PUSHW_1 23040 /* ...|theta|360.0| */ ADD /* ...|theta+360.0| */ PUSHW_1 -12 /* ...|theta+360.0|-12| */ JMPR /* ...|theta+360.0| */ /* goto L2 */ EIF PUSHB_2 64 64 /* ...|theta|sign_sin=1.0|sign_cos=1.0| */ ROLL /* ...|sign_sin|sign_cos|theta| */
最後でsinとcosに対応する符号(1.0で初期化)をスタックに用意した。これは次で利用する。
次に、の場合に対応する。
となることより、の場合は、
- sinの符号を反転
DUP /* ...|sign_sin|sign_cos|theta|theta| */ PUSHW_1 11520 /* ...|sign_sin|sign_cos|theta|theta|180.0| */ GT /* ...|sign_sin|sign_cos|theta|theta>180.0| */ IF /* ...|sign_sin|sign_cos|theta| */ /* if (theta>180.0) */ PUSHW_1 23040 /* ...|sign_sin|sign_cos|theta|360.0| */ SWAP /* ...|sign_sin|sign_cos|360.0|theta| */ SUB /* ...|sign_sin|sign_cos|360.0-theta| */ ROLL /* ...|sign_cos|360.0-theta|sign_sin| */ NEG /* ...|sign_cos|360.0-theta|-sign_sin| */ ROLL /* ...|360.0-theta|-sign_sin|sign_cos| */ ROLL /* ...|-sign_sin|sign_cos|360.0-theta| */ EIF
次に、のときに対応する。
となることより、となるときは、
- cosの符号を反転
DUP /* ...|sign_sin|sign_cos|theta|theta| */ PUSHW_1 5760 /* ...|sign_sin|sign_cos|theta|theta|90.0| */ GT /* ...|sign_sin|sign_cos|theta|theta>90.0| */ IF /* ...|sign_sin|sign_cos|theta| */ /* if (theta>90.0) */ PUSHW_1 11520 /* ...|sign_sin|sign_cos|theta|180.0| */ SWAP /* ...|sign_sin|sign_cos|180.0|theta| */ SUB /* ...|sign_sin|sign_cos|180.0-theta| */ SWAP /* ...|sign_sin|180.0-theta|sign_cos| */ NEG /* ...|sign_sin|180.0-theta|-sign_cos| */ SWAP /* ...|sign_sin|-sign_cos|180.0-theta| */ EIF
0度と90度の時は場合分けして値を出すようにしてみた。まあそんなことしなくても結構な精度で計算してくれるのだけれど。
/* CORDIC algorithm comes here */ の部分は、実際には上で作ったようなCORDICアルゴリズムによる角度計算のプログラムを入れる。
DUP /* ...|sign_sin|sign_cos|theta|theta| */ PUSHB_1 0 /* ...|sign_sin|sign_cos|theta|theta|0.0| */ EQ /* ...|sign_sin|sign_cos|theta|theta==0.0| */ IF /* ...|sign_sin|sign_cos|theta| */ /* if (theta == 0.0) */ POP /* ...|sign_sin|sign_cos| */ PUSHW_2 /* ...|sign_sin|sign_cos|0.0|64.0| */ 0 4096 /* ...|sign_sin|sign_cos|64sin0 = 0|64cos0 = 64*1.0| */ ELSE DUP /* ...|sign_sin|sign_cos|theta|theta| */ PUSHW_1 /* ...|sign_sin|sign_cos|theta|theta|90.0| */ 5760 EQ /* ...|sign_sin|sign_cos|theta|theta==90.0| */ IF /* ...|sign_sin|sign_cos|theta| */ /* if (theta==90.0) */ POP /* ...|sign_sin|sign_cos| */ PUSHW_2 /* ...|sign_sin|sign_cos|64.0|0.0| */ 4096 0 /* ...|sign_sin|sign_cos|sin90 = 64.0|cos90 = 0.0| */ ELSE /* theta != 0.0 and theta != 90.0 */ /* ...|sign_sin|sign_cos|theta| */ /* CORDIC algorithm comes here */ /* ...|sign_sin|sign_cos|64sin|64cos| */ EIF EIF /* ...|sign_sin|sign_cos|64sin|64cos| */ ROLL /* ...|sign_sin|64sin|64cos|sign_cos| */ MUL /* ...|sign_sin|64sin|sign_cos*64cos| */ ROLL /* ...|64sin|sign_cos*64cos|sign_sin| */ ROLL /* ...|sign_cos*64cos|sign_sin|64sin| */ MUL /* ...|sign_cos*64cos|sign_sin*64sin| */ SWAP /* ...|sign_sin*64sin|sign_cos*64cos| */
関数化
さて、これらの計算を関数としてまとめる。関数0とした。
initial stack: ...|angle (deg) (F26Dot6)| final stack: ...|64*sin|64*cos|
PUSHB_1 0 FDEF DUP PUSHW_1 23040 GTEQ IF PUSHW_1 23040 SUB PUSHW_1 -13 JMPR EIF DUP PUSHB_1 0 LT IF PUSHW_1 23040 ADD PUSHW_1 -12 JMPR EIF PUSHB_2 64 64 ROLL DUP PUSHW_1 11520 GT IF PUSHW_1 23040 SWAP SUB ROLL NEG ROLL ROLL EIF DUP PUSHW_1 5760 GT IF PUSHW_1 11520 SWAP SUB SWAP NEG SWAP EIF DUP PUSHB_1 0 EQ IF POP PUSHW_2 0 4096 ELSE DUP PUSHW_1 5760 EQ IF POP PUSHW_2 4096 0 ELSE NPUSHW 10 57 114 229 458 916 1833 3666 7331 14648 29184 PUSHW_4 8340 3 16384 4096 MUL MUL ADD PUSHW_4 10506 6 16384 4096 MUL MUL ADD PUSHW_4 4096 11 16384 4096 MUL MUL ADD PUSHB_1 14 MINDEX PUSHW_1 4096 MUL PUSHW_2 4096 4096 PUSHB_1 0 PUSHB_1 64 PUSHB_1 6 MINDEX ROLL PUSHB_1 1 ADD ROLL PUSHB_1 128 MUL ROLL PUSHB_1 3 CINDEX PUSHB_1 12 LTEQ IF PUSHB_1 5 MINDEX PUSHB_1 5 MINDEX DUP PUSHB_1 5 CINDEX DIV PUSHB_1 3 CINDEX PUSHB_1 6 CINDEX DIV ROLL SWAP PUSHB_1 5 CINDEX PUSHB_1 9 CINDEX LT IF ADD ROLL ROLL SUB SWAP PUSHB_1 5 MINDEX PUSHB_1 5 MINDEX PUSHB_1 5 MINDEX PUSHB_1 7 MINDEX ADD ELSE SUB ROLL ROLL ADD SWAP PUSHB_1 5 MINDEX PUSHB_1 5 MINDEX PUSHB_1 5 MINDEX PUSHB_1 7 MINDEX SUB EIF PUSHW_1 -85 JMPR EIF POP POP POP PUSHW_1 6745 DUP ROLL PUSHW_1 4096 MUL SWAP DIV ROLL PUSHW_1 4096 MUL ROLL DIV ROLL POP EIF EIF ROLL MUL ROLL ROLL MUL SWAP ENDF
参考
*1:こんな風にMathJaxで数学記号が出せるのほんと楽しい。