Fri Jan 17 2020

awk のプロコン用ライブラリを作りたい

構造体もないし, まともな変数スコープもない. 例えば関数の中で for を回す時に変数 i なんて宣言すれば, 他のブロックの中の i が壊れる. ライブラリ化する上ではそういうところに気を使わないといけない.

awk はよくあるスクリプト言語のように数値に int も float も区別がない. かと言って Ruby のように気が利いてるわけでもない. たぶん内部的には全部 float64 なんだと思う. 精度が壊れてない間はあたかも int かのように見せてくれるが桁が大きくなるとすぐ壊れる. たぶんよくある javascript の実装に近い.

それだと困るので int64 を実装したい.

int32 くらいの精度は素の数値でも担保出来てると思う. つまり \(\pm 2^{31}\) くらいはそのまま持てる.

$ awk 'BEGIN { print 2147483647 }'
2147483647

$ awk 'BEGIN { print 2147483648 }'
2.14748e+09

確かにそのようだ.

で例えば整数 \(x\) を適当な定数 \(E\) の下で \(x = x_0 + x_1 E\) と分解して \(x\) の代わりに \(x_0, x_1\) を持つことにする. \(x_0, x_1\)\(\pm 2^{31}\) に入ってればいいんだけど, 掛け算をするときは一時的に掛け算しないといけないので(それはそう), \(2^{31}\) ギリギリどうしの掛け算しちゃうと \(2^{31}\) を超えてしまう. だからそのルートをとって \(\pm 2^{15}\) の範囲に抑えておかないといけない. キリ良くしておいて \(\pm 10^4\). それに合わせて \(E=10^4\). 結構小さいなあ. これで真面目に64bit整数にしようとしたら数を5つにして \(\sum_{i=0}^4 x_i E^i\) にすることになる. 掛け算とか10進数表示にするのとか, 多いと大変さが線形以上に増すのでやりたくくない.

言うて, 書いてみたら案外書けるものだった.

\(\pm 10^{12}\) 同士の和積が出来る. 手元のランダムテストしたけど大丈夫っぽそうかな.

関数のローカル変数がない問題

function f(x) {
    x = 1
    y = 2
}
{
    x = 0
    y = 0
    f(x)
    print x, y  # 0, 2
}

関数 fx は引数で, それ以外に y という変数を参照している. 残念ながら変数には引数であるか, さもなくばグローバル変数でしかない. だからローカル変数のつもりで y を使うと誤って破壊してしまう危険性がある.

www.gnu.org/software/gawk/manual/html_node/Variable-Scope.html

引数はちゃんと引数であることを利用して, 関数の中で使うローカル変数を引数として列挙してしまうテクがあった. 呼び出す場合は空にしておいて問題ないらしい.

function f(x, y) {
    x = 1
    y = 2
}
{
    x = 0
    y = 0
    f(x)
    print x, y  # 0, 0
}

自分ルールだけど, function f(x, _, y) と書くことにする. _ 以降は不要なただのローカル変数の列挙であることを意味するということにする.