定数、変数の四則演算

ここまで基本的なスクリプトの書き方と動作について解説してきましたが、ここではパラメータ、つまり引数に焦点を当ててみます。

サンプルでも print (a + b) なんてものを何気に使っていましたが、
この演算記号はどこまでOKなのでしょうか。

また if文も使いましたが、どんな言語でも普通 AND OR で複数の判定式を含める事が出来ますが「猫の手」ではどこまで許されるのでしょうか。

スクリプトを使うだけならば気にする必要はありませんが(通常対応できないほど複雑な式はスクリプトでは書かないのが吉)
メカニズムを知っておくと、どういう書き方が効率が良くてどういう書き方がNGなのかを知る事ができます。


通常、データベースなどで数値を管理する場合、そこには演算記号を含める事はできません(対応している場合もあります)。

レイアウトのデータを設定したい時に、640x480の座標系で管理し、実データは 1280x780の座標系で設定したい場合があるとします。

X : 130 / 640 * 1280
Y :  90 / 480 *  768

座標が(130,90) だとこのように変換した結果が受け取りたい値iになり、データベースに入力する数値は

260
144

です。

しかしこの後(130,90)を調整した値を再入力したい場合、逐一計算し直さないといけません。

(130,90)を入力させ、プログラム側で受け取った後変換するという方法もありますが
座標系にも調整が入ったりすると座標系の情報も別途入力しなくてはならなかったり、
座標系専用の入力コマンドを作らなければならなかったり、
何より固定値なのに取得のたびに計算処理が走るという無駄にも繋がります。


こういう時、スクリプトならこう書く事が出来ます。

X = 130 / 640 * 1280
Y =  90 / 480 *  768

X,Y の変数に代入する書式ですが、コマンドの引数として渡す場合も同じです。

SETXY( 130 / 640 * 1280,  90 / 480 *  768 )

これらの演算は「猫の手」がコンパイル時に行うので解析ルーチンの処理負荷とは無関係です。
(あまりに複雑な演算をさせるとコンパイル時間が遅くなります)

演算された結果はデバッグ情報で見る事が出来ます。


  sample\tool\compiler.exe を実行
  メニュー「編集」->「オプション」
  で設定ダイアログを開き「デバッグ情報の出力」をチェック

 この状態でコンパイルすると
 INFO.TXT    ラベル情報
 command.txt    コマンド、変数情報
 _スクリプト名  演算、デファイン、マクロ展開後の最終的に使用される最終スクリプトソース
が出力されます。

 頭に '_'(アンダーパー)のついた最終スクリプトファイルを見ればどう解釈されるのかが分かります。

スクリプトにしてみました。

sample04.mlc
sam08_01.png

exec04.bat を実行してみます。
sam08_02.png

おや、結果が 0 になっていますね。なぜでしょう。

_sample04.mlc を開いてみます。
sam08_03.png

これがコンパイラ上の変換を行った後の最終的に使用されるスクリプトです。

先頭、末尾にヘッダー、フッターが付加、
 代入の '=' が '@' に変換され、
 マクロ展開やヘッダー等で行カウントされない行の先頭に '@' が入っています。

ここで 0 になっていますね。
「猫の手」で演算された時に既に 0 になっているという事です。

数値の四則演算の場合、括弧が付いていなければ前方から行われます。

まず
 130 / 640
  90 / 480
の演算が行われますが、整数なのでこの結果はどちらも 0 になります。

その後、
 0 * 1280
 0 *  768
を演算してもやはり 0 です。

このように書き直してみました。
 X = 130 * 1280 / 640
 Y =  90 *  768 / 480

sam08_04.png
ちゃんと演算されました。

掛け算を先に行う事で値が 0 にならず演算できたのです。

また小数点演算に変える事でも解決します。

 X = 130.0 / 640.0 * 1280.0
 Y =  90.0 / 480.0 *  768.0

数値に .0 を付ける事で小数点演算がされますが、変数は整数型のみなので X,Y に代入した時点で小数点は切り捨てられ、
結果は整数で表示されています。

では数値演算の場合、値の大きさはどこまでの数がOKなのでしょうか。

 X =  999999 * 999999
 print X

結果は
-729379967
ですが実際の答えは 9.99998e+11 です。
数値が大きくなりすぎて裏返ってしまいました。

数値の演算は 符号付き32bit 、数値にすると 2147483648 が上限です。


次に変数を含めた演算です。

 X = 130
 Y =  90
 X = X * 1280 / 640
 Y = Y *  768 / 480

冒頭の式の頭を変数に変えただけのものてすね。

実行結果は
sam08_05.png
です。

X は合っているのに Y の値は違っています。

_sample04.mlc を確認してみます。

 X@130
 Y@90
 X@X*2
 Y@Y*1

数値演算の部分が先に計算されています。

1280 / 640 は 2 なので先に計算しても問題ないですが、768 / 480 は小数点が切り捨てられて 1 になります。

正しい結果を得るためには以下のように括弧で括ります。

 X = (X * 1280) / 640
 Y = (Y *  768) / 480

解決しますが、これだと定数でありながら積と商の演算が2度行われる事になります。
X においては括弧で括らずとも結果が正しいので X は括弧で括らない方が解析処理が高速になります。

近年はマシンスペックが高いのでこのくらいの速度効率を考慮する必要はありませんので
あまりに煩わしいという報告があれば、逐一括弧を入れる必要がないように対応するかもしれませんのでご報告をお待ちしています。

また変数のみの演算の場合にも注意点があります。

 A = 5
 B = 2
 C = 10
 print A * B / C

 A(5) * B(2) = 10
 10 / C(10) =

で答えは 1 のはずですが結果は 0 になっています。

これは変数の演算が定数とは逆に後方から行われるためです。

 B(2) / C(10) = 切り捨てられて 0
 A(5) * 0 = 0

解決するためにはやはり括弧で括ります。

 print (A * B) / C

これは積と商の演算順位が同列であるためにシステムの都合順に行われるためで、
特に対応していないのはスクリプトでそんな複雑な演算式を書いた実例がないためです。

これも要望が多いようであれば対応するかもしれません。

 

C言語のdefine定義と同様に以下のように書く事もできます。

sam08_06.png 

define は引数指定も有効です。

sam08_07.png 

defineの引数には変数の演算と同様の注意点があると考えてください。(*と/が混在する場合優先したい方を括弧で括る)

enum も同様です。
sam08_08.png 

enum, define の書式はC言語のドキュメントを参考にしてください。
※全てを再現するものではありません。定義済みマクロ(__FILE__など)には対応していません。

以下は色々な書き方を試してみたものです。
sample04a.mlc
sample04b.mlc

exec04a.bat, exec04b.bat を実行する事で確認できます。

サンプルにもあるように
c=(((((((((((((((((A+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1) //!< assertする. ()階層の最大は PARE_MAX(fpc.h).
という式はASSERT します。
括弧のネストには限界があります。
※内部で括弧が自動的に追加されるものも含まれます _スクリプト名.mlc で確認可

fpc.h
で設定する PARE_MAX の数値を上げる事で通す事が出来ますが、この数値を超えるほどの演算は処理的には無駄なものと考えてください。

スクリプトコマンドに専用の演算コマンドを追加して、C言語上で演算させる方が効率的です。
また、不要な括弧も無駄に負荷をかける事になります。
上記の例では
c=A+17
と書いても同じですが、処理的には括弧演算が行われるため +1 を 17回ループさせる事になります。


以上のように注意点があると使い難いようにも思えますが、基本的に数値演算は汎用、万能性を上げるためものであり、
スクリプトとは本来複雑な計算式を記述するための物ではない事を念頭に置いておいてください。

結局は複雑な計算式を書いて動作がおかしかった所で、デバッグするためには1つ1つ計算を追って行くしかありません。
その場合もほぼ書き方に間違いがあったというのが原因ですので、スクリプトを書く際にはまず人間がパッと見て式を把握できるものにする必要があります。

 print (1+(A*2 + B*2 + c)*2)*3 + 100
 ↓
 A2 = A*2
 B2 = B*2
 D = A2 + B2 + c
 E = 1 + D*2
 print E*3 + 100

こうしておけば途中の値を確認しやすいです。
中間演算用の変数を用意しておけばそれほど大変ではありません。
どうしても景観が悪いという事であれば、デバッグ時に書き換えて確認するとよいでしょう。

通常は、アプリケーションの製作も同時に進行するものなので、面倒なデバッグ作業をスクリプトサイドにまで広げる事は得策ではありません。
複雑な計算式はデバッグが完了して信頼性のあるもののみ利用します。


パラメータを渡すのみで演算を行わない、または演算の優先順位が必要なほど複雑な計算を行わない場合もあると思います。

そういう時は
 sample\tool\compiler.exe
 と同フォルダにある
 mlc.def
 AUTOCUL=0
を追加すると演算の優先順位を付けないためコンパイル速度が向上します。
※自動的に優先をつけないだけで既に記述してある括弧は優先されます


また、解析ソース内
sample/analyzer/fpc.h(22): #define EXT_CALCULATION    ///< 有効にすると代入(LET)コマンドを ()による演算の優先順位に対応させる(その分重くなる).
を無効する事で get_register() による値の取得に括弧の優先が付かなくなります。
こちらは解析処理レベルなので一切の括弧が考慮されなくなりますが、解析処理速度が向上します。
※if文のみ、括弧優先がないと役に立たない文なので有効にしています

現在のマシンスペックではその違いはほとんど分かりません。携帯機などの場合に試してみるとよいでしょう。

章締

この手の演算を含む解析ルーチンを使用するにあたって必ず問題になるのがその信頼性です。
特にif文判定式を含む場合は、判定条件を加えていっても問題なく動作する事は前提となります。