« 2006年12月 | メイン | 2007年02月 »

2007年01月29日

Flash: 文字列長

Flashのアクションスクリプトのソースコードの中に書ける文字列の最大長はいくつでしょうか。
答えは、65533です。

これは、SWFでスタックに値をPushするときに指定できるデータ長がunsigned shortであることが主な理由だと思われます。
たとえば、以下のようなコードで、長さ3の文字列"ABC"をスタックに積むことができます。

96 05 00 00 41 42 43 00

文字列長は3ですが、型情報(00:文字列)と区切り文字(最後の00)を含むのでPushするデータの長さは3+2=5になります(05 00の部分)。
データ長に指定できる値は0xFFFF = 65535までですから、65535から2を引いた65533文字までがpush可能であるということになります。

これはあくまでSWFに直接書ける文字列の最大長と言うことであって、文字列の連結などを行えば、さらに長い文字列を扱うことはできます。

2007年01月28日

Flash: 複数要素のPush

Flashでは、スタックに値を入れるとき、複数の値を一度にPushすることができます。 たとえば、"1"と"2"を一度にPushする場合は次のようなアクションを書きます。
96 06 00 00 31 00 00 32 00
最初の96はアクション"Push"を表しています。
次の06 00は、Pushするデータの長さです。
青い00の部分はデータ型になっていて、この場合は文字列です。文字列は最後の00が区切りになっているので、この場合は、"1"と"2"という二つの文字列をPushすることになります。
ここで、上のようなPush命令を
push "1", "2"
のように書くこととします。
さて、複数要素を一度にpushした場合、スタック上にはどのようにデータが配置されるのでしょう。push "1", "2"は、
[1]
push "1"
push "2"
と、
[2]
push "2"
push "1"
のどちらと等価なのでしょうか。
これを調べるために、次のようなプログラムを書いてみました。
push "a"
push "1", "2"
subtract
setvar
[96 03 00 00 61 00 96 06 00 00 31 00 00 32 00 0B 1D]
これで、変数aの値が-1ならば[1]、1ならば[2]が正しいということになります。
実行してみると、変数aの値は-1になり、[1]のほうと等価であることがわかります。SWFのスタックはメモリ中のアドレスの小さい方から大きい方へと伸びている(そういう実装の方が素直である)と考えることができそうです。
ちなみに、スタックの伸びる方向がわかったので、上のテストプログラムは、以下のようにさらに短く書くこともできます。
push "a", "1", "2"
subtract
setvar
[96 09 00 00 61 00 00 31 00 00 32 00 0B 1D]

FlashLite: 剰余演算

FlashLiteで使われているSWFバージョン4では、割り算の余りを求める命令(C言語などで言う "%" 演算子)がないということを知りました。でも、ActionScriptの方には、"%" 演算子があるようです。そこで、"%"演算子を含む式がどのようなSWFに変換されるか調べてみました。
b = a % c;
は、
push "b"
push "a"
getvar
push "a"
getvar
push "c"
getvar
divide
intpart
push "c"
getvar
multiply
subtract
setvar
のように変換されます。(SWFはスタックマシンです) これは、
b = a - int(a / c) * c;
というアクションスクリプトが変換されるSWFと全く同じものです。 "%" 演算子は、スクリプト上ではとても短いですが、実際には上のように結構長いコードに変換されてしまうので、サイズが気になる場合は使いすぎないように気をつけた方が良さそうです。 "%"に対応する命令が追加されるのは、SWFバージョン5からです。

2007年01月15日

FlashLiteの数値について

FlashとFlashLiteでは、使用されている数値の表現形式が違うようです。

浮動小数点数の仮数部のビット数を求めるには、演算したときの桁落ちを利用します。
変数 v に2のn乗を代入して、別な変数 w に、 w = v + 1という代入をします。
ここで、w - vの値が0になればv + 1の計算が桁落ちしているので、仮数部はnビット以下ということになります。

この方法で調べると、Flashの仮数部は52ビットのようですので、おそらくIEEE754の倍精度浮動小数点数(符号部1ビット・指数部11ビット・仮数部52ビット)なのでしょう。PCで再生するならこれはとても普通です。

FlashLiteではFlashとは別の挙動を示します。
まず、1073741824(2^30です)に2をかけるとInfinityという文字列になります。符号付き32ビット整数っぽい結果です。

さらに、a = 999999999、 b = 0.00000001 としてa+bを計算すると、134217728.00000001が桁落ちなく計算されます。これは、IEEE754 doubleならば桁落ちしてしまう計算です。

2の-9乗は、0.001953125ですが、これは0.00195312と表示されます。9桁目の5が消えています。ただし、この値に2をかけると、正しく0.00390625が計算されます。表示はされませんが、内部的には9桁目を持っているようです。

32ビット整数で情報量を落とさずに表現できる10進数の桁数は9桁であることを考えると、FlashLiteでの数値の表現形式は、整数部と小数部で32ビットずつ使った固定小数点数であるように推測できます。
サイズは同じなのに、なぜ(Liteでない)Flashと同じように普通の倍精度浮動小数点数を使っていないのかはちょっとわかりません。

この手の仕様というのは、FlashLiteのインタプリタを作っている人は当然知っているはずですが、プログラマ向けには公開されていないものなのでしょうか。どなたかご存知の方がいらっしゃいましたら、教えてください。

2007年01月11日

Flash Lite 1.xで使えない文字

Flash Lite 1.xのアクションスクリプトでは、文字列に使用できない文字があります。

まず、\x00はSWFの中でデリミタとして使われているので使えません。

そのほかにも、正しいShift-JISの並びでないとFlashの開発環境(今使っているのは、Flash Pro 8 です)が望むようにコンパイルしてくれません。

たとえば、

s = "abcdefghij";

のとき、
length(s) と ord(substring(s,4,1)) と ord(substring(s,5,1)) の値はそれぞれ10,100,101ですが、
s = "abc\xffefghij";

の場合は先ほどの3つの値は、(9,101,102)となります。

つまり、Shift-JISとして無効な(\xff)が無視されてしまうのです。
本来なら、(10,255,101)になってほしいつもりでした。

バイナリ2.0カンファレンスのときの発表では、無効な文字より後がカットされてしまう(上の例なら3,0,0 となるはず)と話したのですが、年が明けて改めて試してみたら違う挙動になっていました。
試用期限が切れて製品版を購入してインストールしなおしたりしたので、そのあたりが関係しているのかもしれません。

また、2番目の例と等価なはずの

s = "abc\xff\x65fghij";

の場合は、(8,102,103)になるし、
s = "abc\377efghij";

の場合は、(10,121,101)となります。

121ってなんだろう。ちょっとだけ試してみましたが、\376は116、\375では同じ121になります。
これはあまり深追いしませんが、とにかく正しいShift-JISの並びを与える必要があるということです。

で、これはFlash Proのコンパイラのところの癖みたいなので、SWFを直接いじればたぶん大丈夫だろうということ(を口では言ってましたが試していなかったの)で試してみました。

最初の例の、s="abcdefghij"でSWFファイルを生成して、

perl -ipe 's/abcdefg/abc\xff\x65fg/' test.swf

という変換をかけてから実行してみたところ、望みどおりの値(10,255,101)になりました。

というわけで、Flash Pro以外のツールを使ったり、Flash Proが生成したSWFをさらに変換したりすれば1バイトで255種類のパターンが扱えることになるので、文字列を圧縮データ格納に使いたい人(そんな人他にいないかもしれませんが)はもう少し圧縮率を稼ぐことができそうです。