二進法で表したxの最も左の1のビットの位置λ(x)は, 2を底とする対数lgがあれば簡単だ.
λ(x)=⌊lg (x)⌋
たしかに
(lg 1) => 0 (lg 2) => 1. (lg 3) => 1.5849625007211563 (lg 4) => 2. (lg 5) => 2.321928094887362 (lg 6) => 2.584962500721156 (lg 7) => 2.807354922057604 (lg 8) => 3.になっている.
TAOCPでの最初の方法は浮動小数点演算命令による.
FLOTU y,ROUND_DOWN,x; SUB y,y,fone; SR lam,y,52ここで fone=#3ff0000000000000.
これでうまく行く理由だが, MMIXの浮動小数点はIEEE/ANSI standard 754で, その形は
Sは1ビット, Eは11ビット, Fは52ビットの整数で, S=0なら正, =1なら負. 0<E<2047なら
±2E-1023(1+F/252)
を表す.
だからx=1ならFの部分は0で, Eの部分は1023; x=2ならFの部分 は0で, Eの部分は1024; のようになる. 従って上の命令のように, FOUTU(convert fixed to floating unsigned) で変換し, Eの部分から1023を引いて52ビット右シフトしたのがλになるわけだ.
一方, 浮動小数点を使わないMMIX流の方法は次の通り.
SRU y,x,32; ZSNZ lam,y,32; ADD t,lam,16; SRU y,x,t; CSNZ lam,y,t; ADD t,lam,8; SRU y,x,t; CSNZ lam,y,t; SRU y,x,lam; LDB t,lamtab,y; ADD lam,lam,t;まずxを32ビット右シフトしてyに置く.
ZSNZ lam,y,32はyが≠0なら(つまりxの左半分に1があれば)lamに32を, そうでないなら0を入れる. 次はそのlamに16を足してtに置いておく. lamは16か48である. xを16か48ビット右シフトしてyに置く. そのyが≠0ならlamにtを入れ, そうでないならlamはそのまま. 次は8ビットの範囲で同様なことをするので, lamは最も左の1を含む8ビットの範囲の右の境界の値になっている.
SRU y,x,lamでその範囲をレジスタの右端に移動し, 256バイトの表lamtabを引いてlamに足すのである. lamtabは0の場所は使わない. 1のところは0, 2,3のところは1, 4〜7は2, 8〜15は3, ..., 2k〜2k+1-1はk, 128〜255は7になっている.
最も右のビットを取り出すには x & -x とやったが, 左に対してはうまい方法はない. 「ハッカーのたのしみ」の著者 Warrenの考えた方法というのはこうだ.
y ← xとする. レジスタの長さを2dとして0 ≤ k <dについて y ← y | (y » 2k)とする. y - (y » 1) がxの最も左ビットである.
yにあるxの最も左のビットは次々とシフトされて右方向に2ビット, 4ビット, 8ビットと広がり, レジスタの右端まで1で埋める(塗りつぶす). 他の右の方にある1はこの塗りつぶしに埋没する. 左端の0の並びはそのままである. そこでyからyを1ビット右にシフトしたものを引くと, xの最も左のビットがとれるわけだ.
TAOCPには
λx = λy if and only if x ⊕ y ≤ x & y
という式がある. これも証明は簡単だ.
x, y の最も左のビットの位置が同じとすると, x ⊕ yのその位置は0になり, x & yのその位置は1になるから, ⊕の方が小さい. 最も左の1の位置が異なると, より左にある1の位置では, ⊕は1, &は0なので⊕の方が大きいことになる.
気になるのは等号の場合だ. x, yが共に0の時, λxもλyも不定になる. 不定同士を=にしたいとすると, ⊕も&も0だからこの場合は等号になるのであろう.
0 件のコメント:
コメントを投稿