Vz resources : MACRO FAQ
2008-06-22T20:46:04+09:00

VZ Macro FAQ - 式と演算

演算子の優先順位を確認するには

演算子の優先順位はマニュアルにも記述されていますが,ソースファイルにこの上なく確実な一覧がありますので,一度確認することをお勧めします。

VZ v1.60ソースの EXPR.ASM の129行目から始まる「Operater table」です。

;--- Operater table ---

tb_operator     db      "[ ] ++--~ ! !!"
IFDEF NEWEXPR
                db      "... "
ELSE
                db      ". .."
ENDIF
                db      "<<>>& ^ | * / % + - < <=> >===!=&&^^||"
                db                          "= [=&=^=|=*=/=%=+=-="

「NEWEXPR」の定義部分は,v1.60 とそれ以前でポインタ演算子の優先順位が逆転したことを示しています。

べき乗値を算出するには

VZにはべき乗を算出する演算子はありませんので,単純な乗算をループする必要があります。

1 ^\ "" ?.
        &g("n^m ?") &Cr (r<1)?{ &m(40) .}
        &f($5e,pw) (r<1)?{ &m("'^'がないか位置が不正です") . }
        &s
        ps[=pw, #61 #<          ;#61:文字列の選択複写窓内で数値を取得
        &n a=r,
        #f                      ;凄い手抜き(^^;
        &n b=r,
        #[ ps],                 ;窓を閉じる
        &d
        (a && b>1)??{ &m("入力値が不正です:[%s]",pw) . }
        pu..1-, pu..0=a,
        (b-1){ &Im(pu,a) }
        &m("%dの%d乗は%,luです.",a,b,pu..0,pu..1)

十六進数を記述するには

十六進数を通常のテキスト中で示すときは,アセンブラでの記述のように「1F7Ah」と「h」を付加して十進数と区別することが多いと思いますが,VZマクロで十六進数を記述するときは,「$」を前置して「$1F7A」のようにします。C言語などのような「0x1F7A」といった記述はエラーになってしまうので注意しましょう。

なお,VZにおいて「$」は色々な意味で用いられます。

マクロ冒頭の「?.」とは何かのお呪いですか

(s)?.」と同じです。

VZはグローバルマクロの実行に先立って,起動時のシステムモード(変数s)を評価します。評価した結果が内部レジスタに格納された上でマクロの実行が開始されますので,本来なら「(s)?.」と記述するべきところを,マクロ冒頭に限り「?.」と省略してもよいことになっています。

なお,「(s)?.」の意味は,「編集モードでなければ終了」ということです。わかりやすく書くと「(s!=0)?.」となり,「?.」は省略の上に省略が重ねられていることになります。

お呪いではなくきちんとした意味があるのですが,マクロ入門時は編集モードで動作するマクロを書くのが普通(VZはテキストエディタですよ!)でしょうから,お呪いのように書いておくと安全でしょう。

(s)」のような記述は式なのでしょうか

正確には括弧の内側だけが式で,括弧は式を評価するキーワードです。通常の式の中でも括弧は演算の優先順位を変更するために使われるので注意が必要ですが,通常の式はカンマ(,)が終端を示すキーワードです。「(式)」は評価した結果を条件分岐に使用しなければ通常の「式,」と同じです。

(s)」のように変数名だけを記述するという形式は,右辺や演算子のある通常の式では見られないものなので,一見難しく思えるかもしれませんが,式の評価である「真」と「偽」がどういうものであるかを理解すれば疑問は氷解するでしょう。

値が0なら偽であり,0以外なら真なのですから,例えば変数sが0(編集モード)であれば偽となり,変数sが1〜4(入力窓・コマンドライン・ファイラー・メニュー等)であれば真となります。要するに「(s!=0)」の省略形ということになります。

(a && b)??{ … }」はどういう意味ですか

「ド・モルガンの法則」を利用した式で評価しています。少し分かりやすい等価表現で書き直すと次のようなコードになります。

    (a==0 || b==0)?{ … }

aあるいはbのいずれかが0なら次のブロックを実行することになります。先のコードは,aとbが共に0でないという条件が成立しなければ次のブロックを実行する,…と言い換えることができるでしょう。要点は論理演算子が「||」から「&&」に転ずることと,条件分岐判断が「?」から「??」に反転するところです。

さて,「ド・モルガンの法則」は「逆もまた真なり」であるところが面白いところです。先のコードとまったく逆の条件も当てはまります。並べてみましょう。

    (a==0 || b==0)?{ … } → (a && b)??{ … }
    (a==0 && b==0)?{ … } → (a || b)??{ … }

次のような論理式の方が理解しやすいという人もいらっしゃるでしょう。

    (!A) || (!B) → !(A && B)
    (!A) && (!B) → !(A || B)

高級言語による次のコードも「ド・モルガンの法則」を適用したものです。

    if (!(x >= 0 && y >= 0)) { … } → if (x < 0 || y < 0) { … }

(r|1+1)??.」とはどういうことですか

&g()の入力窓で,[ESC]でキャンセルしたときの返り値rは-1で,何も文字を入力せずに[CR]で窓を閉じたときの返り値rは-2です。この判定式は,それらのキャンセルとして扱う操作が行われると即座にマクロを終了するためのものです。

-1と1の論理和は-1で,-2と1の論理和も-1です。rが-1でも-2でも,r|1は-1ということになります。-1に1を足すと0ですから,(r|1+1)は偽になります。条件分岐キーワードを「??」と重ねて真偽を逆転させ,式が偽なら終了キーワード「.」を実行させます。

(-1 | 1)
    1111 1111 1111 1111     (-1)
 |  0000 0000 0000 0001     ( 1)
 ----------------------
    1111 1111 1111 1111     (-1)

(-2 | 1)
    1111 1111 1111 1110     (-2)
 |  0000 0000 0000 0001     ( 1)
 ----------------------
    1111 1111 1111 1111     (-1)

ビット列から値を取り出すには

ビット列とは二進数の表現で,0をオフ,1をオンとビットごとに見立てた数の並びを言います。通常は十進数と同様に,左側を上位の桁,右側を下位の桁として見るのが普通です。特にビット列と言った理由は,二進数はビット列で説明する方が簡単だからです。

ビット列から値を取り出すにはビット演算を行う必要があります。取り出す手法としては,「ビットシフト」(以下,シフトと言う。)と「ビットマスク」(以下,マスクと言う。)があります。シフトはビット列を左右にずらす手法で,演算子は「<<」(左シフト)と「>>」(右シフト)を使います。マスクは元のビット列(値)に特定のビット列を重ね合わせる手法で,「&」(ビットごとのAND)「^」(ビットごとのXOR),「|」(ビットごとのOR)などの二項演算子を用います。また,ビット列を反転させる単項演算子「~」(ビット反転,1の補数)を使うこともあります。

実例は,以下の項目を参照してください。

これらの項目では,FCBにパックされた日付や時刻から,年月日と時分秒を取り出してみる実例を示しています。

例えば,FCBに格納されている日付データが34A3hで,時刻データが2B35hだとします。

日付データである34A3h(十進数で13475)のビット列は次のようになります。(見やすいように4桁毎に区切ってありますが,元は16桁がつながっています。)

 0011 0100 1010 0011

日付データは,この16ビットをいくつかに分けて年・月・日に割り当てており,年が7ビット,月が4ビット,日が5ビットです。なぜこうなっているかは別の適当な資料を参照してください。

 0011010  0101  00011
 -------  ----  -----
   年      月    日

一方,時刻データである2B35h(十進数で11061)のビット列は,次のとおりです。

 0010 1011 0011 0101

時刻データは,時が5ビット,分が6ビット,秒が5ビットです。24時間の時と60秒の秒が同じ5ビットに割り当てられているというのは変なのですが,パックされたタイムスタンプの秒は2秒単位の偶数しか表現しないので,5ビットに納まるということになっています。当然,取り出した値は2倍しなければいけないことになります。

 00101  011001  10101
 -----  ------  -----
  時      分     秒(2秒単位)

16ビットで表現できる数の範囲はどのくらいですか

ビット数により表現できる数を表にしておきます。ビット数に対応する最大値は,二進数ですべて1になっている状態です。(1になっていることを「ビットが立っている」と表現することがあります。)

ビット数表現可能数量最大値最大値の十六進/二進
1210001h/0000 0000 0000 0001
2430003h/0000 0000 0000 0011
3870007h/0000 0000 0000 0111
41615000Fh/0000 0000 0000 1111
53231001Fh/0000 0000 0001 1111
66463003Fh/0000 0000 0011 1111
7128127007Fh/0000 0000 0111 1111
825625500FFh/0000 0000 1111 1111
951251101FFh/0000 0001 1111 1111
101024102303FFh/0000 0011 1111 1111
112048204707FFh/0000 0111 1111 1111
12409640950FFFh/0000 1111 1111 1111
13819281911FFFh/0001 1111 1111 1111
1416384163833FFFh/0011 1111 1111 1111
1532768327677FFFh/0111 1111 1111 1111
166553665535FFFFh/1111 1111 1111 1111

つまり16ビットで扱える数の量は65536,範囲は0から65535になります。ただしこの範囲は符号のない場合で,通常は符号付きの整数となるので,数値の範囲は -32768から32767の範囲になります。これでは解りにくいので図を見てください。

   0             32767 -32768            -1
  ┌─────────┬─────────┐
  │     正の範囲     │     負の範囲     │
  └─────────┴─────────┘
   0000h         7FFFh 8000h          FFFFh

見ての通り,0から始まって32767(7FFFh)までは正の数ですが,それに1を加えると扱える整数の最小値である負の-32768(8000h)となり,そこから-1(FFFFh)まで増えて(数字は減って)いきます。

16ビットの整数は,最上位の16ビット目が立っていなければ正の数で,立っていれば負の数になります。どの数が正負の境界になっているかというと,十六進数で8000hです。この数を二進法で表わすと「1000 0000 0000 0000」となり,最上位ビットが立っていることがわかりますね。

計算や比較では符号付きになりますが,書式制御(%u)により符号なしの整数として出力することができます。

    &?("%u",$FFFF)

この表を見ると,FCBにパックされた日付・時刻データは,12必要な月が4ビット,31必要な日が5ビット,60必要な分が6ビット必要であることがよくわかると思います。

以下の項では,それぞれの値の取出し方を示します。なお,ビット列の中でのデータの割り当てられている位置によって取出し方が異なりますので,上位寄り,中寄り,下位寄りの順に説明します。

ビット列の中で上位寄りにある値を取り出すには

FCBにパックされた日時データのうち上位寄りの値は,年と時です。年は上位7ビットに,時は上位5ビットに格納されていますので,同じ方法で取り出してみましょう。

上位寄りの値を取り出すには,ビット演算子の「>>」(右シフト)を使います。右シフトとはビット列を右に向かってずらすことですが,ずらして右側にはみ出した分は捨てられ,左側は0で埋められます。年は7ビットに入っているので9ビット(16-7)ずらします。

 0011010010100011
 >>9
          0011010010100011
 ~~~~~~~~~-------_________
   │       │     │
   │       │     └─ 捨てられる
   │       └─ 保持される
   └─ 0が埋められる

 0000000000011010
「$34A3>>9」の演算結果(十進数で26)

26年というのは変ですが,これは1980から起算しているためです。したがって,1980を足してやって2006年ということになります。年は7ビットで128年分あり,2108年1月1日がXデーであることがわかりますね。

同様に時を取り出してみます。時は5ビットに格納されていますので11ビット(16-5)ずらします。

     0010101100110101
 >>11
                0010101100110101
                -----
     0000000000010101

 「$2B35>>11」の演算結果(十進数で5)

時はそのままで,5時ということになります。

ビット列の中で中寄りにある値を取り出すには

前後のビット列を捨てるという考え方をすればよいので,「<<」(左シフト)と「>>」(右シフト)を使えばよいことがわかりますね。次の例は,FCBに格納されている日付データが34A3hで,時刻データが2B35hだとして説明します。

まず日付データの34A3hの上位側を,左シフトであふれさせて捨てます。

         0011010010100011
 <<7
  0011010010100011

         0101000110000000
  -------         -------
    │             │
    │             └─ この7ビットは0が埋められる
    │
    └─ この7ビットは捨てられる

この時点での値は5180hです。

次に右シフトで下位側を捨てます。シフトする量は,上位と下位の要らないビットを合わせた数になります。月は4ビットなので12ビット(16-4)を右シフトします。

         0101000110000000
 >>12
                     0101000110000000
         0000000000000101
         ------------    ------------
           │              │
           │              └─ この12ビットは捨てられる
           │
           └─ この12ビットは0が埋められる

これにより中寄りのビット列が取り出されます。式で書くと ($34A3<<7)>>12 == 5 となりますが,演算子の優先順位は左シフトが上なので,括弧がなくても結果は同じになります。したがって,$34A3<<7>>12 == 5 で,5月ということになります。

同様にパックされた時刻データから分を取り出すには,月の5ビットと秒の5ビットをシフトしてやり,$2B35<<5>>10 == 25 で,25分ということになります。

ビット列の中で下位寄りにある値を取り出すには

中寄りの値を取り出すのと同じように左右のシフトで下位寄りの値を取り出すこともできるのですが,マスクという手法で簡単に取り出すことができます。マスクは塗装などをするときに覆いをするマスクと同じ意味で,何かを被せて一部を隠し保護するという手法です。これにはビット演算子の「&」(ビット毎の積:AND)を用います。

まずは一番オーソドックスなマスキング例を示します。ある数値の上位8ビットと下位8ビットに分けて数値が格納されているとき,下位8ビットだけを取り出すにはFFhのマスクをかけます。

     1000 1000 0101 0101
  &  0000 0000 1111 1111
 -----------------------
     0000 0000 0101 0101
     |    |     | |  | |
     |    |     +-+--+-+-- 1 & 1 == 1 で保護される
     |    |
     +----+-- 1 & 0 == 0 で塗り潰される

このように元の数値の取り出したいビットに対応するマスク側の数値のビットを立てて論理積を求めることにより,下位寄りのビット列の値だけを取り出すことができます。取り出し方の例として,FCBに格納されている日付データが34A3hで,時刻データが2B35hである場合における,日付データから日を,時刻データから秒を取り出す方法を示します。

日は5ビットに入っていますので下位5ビットだけが立っている値でマスクします。でも,そういう値ってパッと出て来ないですよね。ここで前述したビット数と最大値の表を思い出してください。一部を再度示します。

ビット数表現可能数量最大値最大値の十六進/二進
1210001h/0000 0000 0000 0001
2430003h/0000 0000 0000 0011
3870007h/0000 0000 0000 0111
41615000Fh/0000 0000 0000 1111
53231001Fh/0000 0000 0001 1111
66463003Fh/0000 0000 0011 1111
7128127007Fh/0000 0000 0111 1111
825625500FFh/0000 0000 1111 1111

下位5ビットの立っている値とは,31です。そうなんです,月の最大値と同じなんですね,これが。偶然と言うか何と言うか,もしも一ヵ月が32日だったら6ビット必要ということになって,年のビット数を減らさざるを得なかったでしょうね。大昔のOS開発者は,この偶然に感謝したに違いありません。それはともかく,ビット列の長さ(n)に対応する最大値は,2のn乗から1を減じた数ということがわかります。むしろ十六進数の規則に注目すれば,1・3・7・Fを順番にあてはめていけばよいという簡単な覚え方もあることに気付くでしょう。(麻雀でおなじみのイー・スー・チーでなかったのはちと惜しいですね。)

そんなわけで,下位5ビットの日を取り出すには,31(十六進数で1Fh)のマスクをかけます。式にすると,「$34A3 & $1F」(十進数で「13475 & 31」)です。

       年     月   日

     0011010 0101 00011
 &   0000000 0000 11111
 ----------------------
     0000000 0000 00011
     ~~~~~~~ ---- _____
       │     │   │
       │     │   └─ マスクで保護される
       │     └─ 0で塗り潰される
       └─ 0で塗り潰される

 0000 0000 0000 0011
 「$34A3 & $1F」の演算結果(十進数で3)

同様に下位5ビットの秒を取り出します。式にすると,「$2B35 & $1F」(十進数で「11061 & 31」)です。

      時     分    秒

     00101 011001 10101
 &   00000 000000 11111
 ----------------------
     00000 000000 10101

 0000 0000 0001 0101
 「$2B35&$1F」の演算結果(十進数で21)

はい,ちょっと待ってください。秒は5ビットに入っていて,5ビットで表現できる数の範囲は0〜31ですから,秒は偶数だけの2秒単位になっているのでしたね。したがって取り出した数を倍にしてやらないといけませんから,21 * 2 == 42 ということで42秒ということになります。

ビット列の中で中寄りにある値の取出し方(2)

さて察しのよい人は,「中寄りにある値を取り出すときはシフトとマスクを組み合わせてもよいのではないか」と見当がつくでしょう。演算子の優先順位は >> の方が & よりも高いので,そのまま演算の順序どおりに式を書けます。

月を取り出す式は,「$34a3 >> 5 & $F」になります。

     0011010010100011
 >>5      0011010010100011
 &   0000000000001111
 --------------------
     0000000000000101

 0000 0000 0000 0101
 「$34A3 >>5 & $F」の演算結果(十進数で5)

分を取り出す式は,「$2B35 >> 5 &3F」になります。

     0010101100110101
 >>5      0010101100110101
 &   0000000000111111
 --------------------
     0000000000011001

 0000 0000 0001 1001
 「$2B35 >> 5 & 3F」の演算結果(十進数で25)

ロング整数に負の整数を加算したら増えてしまいます

例えば次のような事例ですね。

1 ^\ "" ?.
        a=-30000,                       ;-30000
        pu..0=0, pu..1=1,               ; 65536
        &Ia(pu,a)                       ;加算すると35536ではなく
        &m("%lu",pu..0,pu..1)           ;101072になる
        &d #] .                         ;$10000+$8AD0=$18AD0 だから

ロング演算関数の&Ia()が符号なしで演算するという仕様のため,このようなことが起こります。したがって変数に負の値が代入される可能性のある場合などは注意が必要です。むしろ正の値しか扱わないときにだけロング演算関数を使うことができると考えるべきでしょう。なぜなら,数値の型が異なる場合は一方に合わせなければならず,それは他方を包含する側にしなければならないからです。

もしも想定できない数が入力されたり,結果的に15ビットに納まらない(16ビットの整数で負になる)数値をロング整数に加算しなければならない場合などは,符号と数値に分けて分岐させるという手段が間違いがないと思われます。

= tm =
* m
1 ^\ "" ?.
        pw[=pa, pw.0-, mx+,             ;-1/-2を取得するためのフラグ
        &g("加算する数")
        mx-, &Cr pw],
        (pa.0 && r)??.                  ;何も入力がないか数値以外は終了
        p+, n=r, (n<0)?{ p-, n=-n, }    ;+/-フラグ
        ;;&t &m("%1s %d","-+"+p,n)
        (p)?{ &Ia(pu,n) }?{ &Is(pu,n) } ;加減により分岐
        &m("%1s %u = %lu","-+"+p,n,pu..0,pu..1)
*

カーソル位置の数字列を数値として取得するには

ショート

16ビットに納まる整数を取得すればよいのであれば,システム関数&nを使います。&nはカーソル位置以降の数字列を数値と見なして取得することができる関数で,取得した値は関数の戻り値としてrに格納されます。

この程度の機能であれば,インスタントマクロ(初期キー:[ESC]@)で確認することが可能です。インスタントマクロの入力窓で「&n&m("%d",r)」と入力し,実行結果を見てみましょう。

ロング

システム関数&nは16ビットに納まらない数値は丸められてしまいます。丸められるとは,はみ出た部分が残る状態で,16ビットの容器を満たした分はそのつど捨てられて,最終的に残った数が認識されるのです。容器を満たした回数がどこかに記録されていれば簡単ですが,残念ながらそのような機能は&nにはありません。

元々持っている機能では実現できないので,数字を数値として一桁ずつ取得しながら桁をくり上げて加算してゆくことになります。

次のマクロは,32ビットに納まる範囲で,カーソル位置の数字列を数値として取得するサンプルです。

= test
* m
1 ^\ "" ;カーソル位置の数字列を数値として取得(ロング対応)
        ?.
        &>                              ;4バイトのバッファを確保
        p=r,                            ;*pをバッファに割り当て
        &Im(p,0)                        ;*pを初期化

 :p     ; ---- カーソル位置から始まる整数をpに取得 ----
        (cd<'0'||cd>'9')?>q             ;数字でなければ終了
        &Im(p,10)                       ;*p *= 10, 桁繰上
        &Ia(p,cd-$30)                   ;*p += n,
        #d                              ;次の文字へ
        >p                              ;繰り返し
 :q     ; ---- Quit ----
        &m("%lu",p..0,p..1)             ;*pを表示
; ==== buffer ====
00:     (3+$)."$[4]"
*

十六進数

カーソル位置の十六進数文字列を数値として取得するには,システム関数&hを使用します。なお,この関数を使用した場合,カーソル位置の数字列が十進数であったとしても,十六進数として取得されることに注意してください。

&h&nと同様に16ビットに丸められます。インスタントマクロで「&h&m("%x",r)」を試してみましょう。

二進数

カーソル位置の二進数文字列を数値として取得するシステム関数は用意されていません。したがってマクロで実現するということになります。

次のマクロは0と1の羅列する文字列上で実行すると,数値として取得するサンプルです。通常は数字列の先頭位置に移動して取得します。カーソル位置以降を取得したい場合は,シフト実行します。

=== bit2dux v0.10 = by H_ear
* mad for VZ!
1 ^\ "二進数値を十進・十六進に変換"
        ?. &s
        #M ly[,                         ;カーソル行位置を記憶
        (ks&1)??                        ;シフト実行ならカーソル位置始点
        #a                              ;数列(等)の先頭へ
        i=j-,
 :L     &SF("01",cd)                    ;カーソル位置が0or1か
        (r)??{                          ;0or1ではない場合で
                (i)?>M                  ;;計算済なら mes & mark で終了
                   ?>v                  ;;計算ショジョ(^^;なら先へ進む
        }                               ;↑fgalpk mes 14 
        j=j<<1+cd-$30,                  ;桁をくり上げながら加算 H-Oka(__)
        i++,
        (i>16)?{ i-, >M }               ;16桁(64k)以上は overflow
 :v     #d
        (ct<2)??>L                      ;1行内のみ対象 夢幻る〜ぷ防止 JUN(__)
 :M     #U ky],                         ;カーソルを元の位置に戻す
        (i)?{
                &m(" It's ... %d  /  %u  /  %02xh",j,j,j)
        }?{
                &m(" Overflow")         ;2進数でない場合も含む
        }
 :Z     
        &d #] .
*

バッファの数字列を数値として取得するには

&e()を使う

文字列を指定してシステム関数&e()を実行します。これにより&e()の戻り値として簡単に取得できます。文字列の指定は先頭アドレスを指定することになるので,&e(ADDR)ということになります。「文字列」ですので,終端にヌル(00h)がなければなりません。

1 ^\ "" ?.
        &> b=r,                         ;バッファを指定
        &Sc(b,"34567")                  ;文字列をコピー,終端はヌル
        &e(b)                           ;バッファ先頭アドレスを指定して実行
        a=r,                            ;戻り値を変数に格納して表示
        &m("数値は:%d (%u) / 文字列は:%s",a,a,b)
        .
0:      ; ---- buffer ----
        (3+$)."$[8]"

バッファに埋め込む数字列を色々変更して試してみると,数値として取得できたことが確認できるでしょう。32768($8000)以上にすると,符号付き整数で表示した方は負数になります。

バッファの文字列を指定するときはアドレスで指定することになりますが,変数にバッファの先頭アドレスが格納されていれば,アドレスを指定したのと同様の結果を得ることができます。

ポインタ演算子と単項演算子の混合式で結果が不定になります

複数の演算子を組み合わせる混合式では,演算子の優先順位の影響で思わぬ結果をもたらす場合がありますので注意が必要です。特にポインタ演算子は二項演算子の中でも最も優先順位が高く,その見かけ上からも結合が強そうに感じてしまいがちですが,単項演算子の優先順位が二項演算子よりも高いという原則は変わりません。

インクリメント× v..0++,v..0+=1, または ((v..0)++,
デクリメント× v..0--,v..0+=1, または ((v..0)--,
プッシュ× v.0[,r=v.0, r[, または ((v.0)[,
ポップ× v.0],v.0=0],

なお,初期化によく使われる 0 または 1 の代入においては,v.0+, や v..0-, と書くことが可能です。なぜ優先順位に反してこのような式が通るのかというと,v..0-, は v..0=-, の省略形であると解釈されるからです。このような解りにくい書き方を気にしながら書くくらいなら,最初から v..0=0, のように書いた方が安心ですね。

ビット演算子の効果的な用法がありますか

例えば,入力待ちで数字や記号の場合は文字コードが固定していますから簡単ですが,英字の場合は大文字と小文字の文字コードを判別しなければならないので,式が次のように煩雑になります。

        (k=='A' || k=='a')?{ …処理… }

このようなときに活用したいのがビット演算子です。

'A'(41h)と'a'(61h)をビットで表現すると次のようになります。

'A' 41h  0000 0000 0100 0001
'a' 61h  0000 0000 0110 0001

違いは下位から6番目のビットだけですから,この2の5乗の値である32…つまり両者の差(排他的論理和)である20h…のビットが立っていても立っていなくてもよいという式を建てればよいことになります。このような場合は,ビット演算子の「|」(ビット毎の論理和)を効果的に使用して式を簡潔にすることができます。

'A' 41h  0000 0000 0100 0001    小さな方の値と
32  20h  0000 0000 0010 0000    両者の差の値とのビット毎の論理和は
'a' 61h  0000 0000 0110 0001    大きな方の値となる

'a' 61h  0000 0000 0110 0001    大きな方の値と
32  20h  0000 0000 0010 0000    両者の差の値とのビット毎の論理和は
'a' 61h  0000 0000 0110 0001    大きな方の値となる(変わらない)

        (k=='A' || k=='a')?{ …処理… }
                ↓
        (k|32=='a')?{ …処理… }

少し難しくなりますが,ビット毎の論理積「&」を使っても同じ様なことができます。

'a' 61h  0000 0000 0110 0001    大きな方の値と
95  5Fh  0000 0000 0101 1111    両者の共通ビットを立てた値との論理積は
'A' 41h  0000 0000 0100 0001    小さな方の値となる

'A' 41h  0000 0000 0100 0001    小さな方の値と
95  5Fh  0000 0000 0101 1111    両者の共通ビットを立てた値との論理積は
'A' 41h  0000 0000 0100 0001    小さな方の値となる(変わらない)

        (k=='A' || k=='a')?{ …処理… }
                ↓
        (k&95=='A')?{ …処理… }

x=y%10+'0'という式は何をしているのですか

'文字'はその文字の文字コードを表わします。したがって'0'はゼロを示す「文字」の文字コードであるため,十六進数で$30(0x30h),十進数で48を表わします。つまり,x=y%10+'0' という式は x=y%10+$30 あるいは x=y%10+48 と等価です。

さて,それでは y%10 という式はどういう結果を得ているかというと,yに代入された値を10で割った余りです。したがってy%10の結果は0から9の範囲にかならず納まることになります。

1バイトの文字コードであれば,単純な足し算で文字コードをずらすことができますので,文字'0'に0から9の範囲で加算すれば文字'0'から文字'9'の範囲の文字になります。

つまり,x=y%10+'0' という式はyに色々な値が格納されていても,'0'〜'9'の範囲の文字コードをxに代入するということになります。

これを応用すると,文字コードの連続した文字の範囲で,数値に対応した文字を表示することができます。

1 ^\ "" ?.
        ax=$2C00, &i($21) rn=cx+dx, &r  ;時刻を種に乱数発生
        r%=10,                          ;乱数を 0〜9 の範囲の数値に丸める
        &m("'%c' / '%c' / '%c'",'0'+r,'0'+r,'A'+r)

文字コードの連続した一定の範囲でグループ化される文字はそれほど多くありません。通常は数字とアルファベット程度です。ひらがなやカタカナは小字や濁点が混じるので工夫が必要です。また「む」以降は,ひらがなとカタカナでコードがずれますので,個別に計算する必要があります。

1 ^\ "" ?.
        ax=$2C00, &i($21) rn=cx+dx, &r  ;時刻を種に乱数発生
        r%=5, r*=2,                     ;乱数を 0〜8 の範囲の偶数値に丸める
        &m("'%c'/'%c'/'%c'/ '%c' / '%c' ",'あ'+r,'か'+r,'さ'+r,'ま'+r,'マ'+r)

乱数を発生させるといつも決まった順序で同じ数になってしまいます

乱数を発生してrに返す関数&rは,常に固定した「r=rn=3+rn*$300」という式で数値を返すシステム関数です。最初に実行した時に乱数の種となるシステム変数rnに格納されている値が同じであれば,決まったパターンの乱数しか発生しません。したがって,乱数を発生させるたびに種となるrnに異なる数値を格納してやる必要があります。

rnに0が格納されている状態で10回乱数を発生させるサンプルです。

1 ^\ "" ?.
        (mr==$80)?. je[-, rn-, (10){ &r &?("%5u",r) #m } je],

実行結果:rrn+3+rn*$300が返されますので最初の実行は必ず3になります。

    3
 2307
10398
26235
22325
20616
12531
  403
15494
25783

何度やっても同じ結果しか出力しません。

これではゲームなど,起動するたびに異なる乱数を要求されるプログラムの場合は,まったくつまらないものになってしまいます。それでは実行するたびに異なる乱数を発生させるためにはどうすればよいでしょう。刻々と変化する値を種にすればよいのです。刻々と変化する値の代表は時刻です。MS-DOSでは通常のファンクションコールで時刻を取得すれば,1秒以内に再実行しない限り異なる種を乱数に与えることができます。

1 ^\ "" ?.
        (mr==$80)?.
        ax=$2C00, &i($21)
        je[-, rn=cx+dx+r, &r &?("%5u",r) #m je],

ファンクションコールで時刻を取得すると,cxに時と分,dxに秒が返されます。なるべく意味のない数値になるようにcxdxを足してしまいます。それで十分ですが,1秒以内に再実行された場合にも対応できるようにrも足してしまいます。このr&m()など戻り値を保存するような命令しか行わずに変化がなかったと仮定しても,前回の乱数が入っていますので,1秒以内の再実行でも異なる乱数が発生することになります。


VZ Macro FAQ - operation
The VZ resources, valid XHTML 1.0 Strict. ©2005-2008 by mezala.