y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

PECL stats のバグ修正 (DCDFLIB, RANDLIB の差し替え)

前回の記事で紹介した PECL stats には、いくつか、正しく動作しない関数があります。利用しているライブラリ (DCDFLIB, RANDLIB) のバージョンが古いためです。

報告されているバグの一つは、こちらです。カイ二乗分布の累積分布の逆関数を計算できない*1といった問題が報告されています。
PHP :: Bug #58394 :: stats_cdf_chisquare() dies when which=2,3

再現コードを書いて、手元でも確認してみます。このバグに関して本質的ではありませんが、計算結果を出力するように echo を追加しました。

<?php
echo stats_cdf_chisquare(0.0525437064989, 283, 2) . "\n";
echo stats_cdf_chisquare(0.0525437064989, 245.56, 3) . "\n";

PECL stats 1.0.3 をインストールした状態で、このコードを実行します。たしかに、正しく計算されずに異常終了します。

$ php bug_58394.php
 SMALL, X, BIG not monotone in INVR

これと同種のバグがいくつか報告されており、対応方法も以下のように指摘されています。C のコードで static キーワードが必要なのに抜けている箇所があるようです。
PHP :: Bug #57831 :: wrong storage class for variable in C functions gscgn() and gssst()

このバグを修正するには、PECL stats のソースコードに含まれる DCDFLIB, RANDLIB を新しいものに差し替えて、拡張モジュールを再ビルドします。

まず、PECL stats のソースコードを取得して展開します。PECL 拡張モジュールのソースコードは、pecl download コマンドで取得できます。

$ pecl download stats
$ tar zxf stats-1.0.3.tgz

次に、ライブラリのソースコードを入手します。DCDFLIB, RANDLIB のソースコードは、それぞれ以下のウェブページから入手できます。ダウンロードボタンをクリックしてファイルを保存する形になります。

これらのライブラリも同様に展開します。DCDFLIB の圧縮ファイル名には空白が含まれているので、以下のようにエスケープして指定しています。

$ tar zxf CDFLIB90\ \ _V90.tar.gz
$ tar zxf RANDLIB_V90.tar.gz

両ライブラリの圧縮ファイルを展開すると、CDFLIB90, RANDLIB90, source というディレクトリが作成されます*2。今回の手順では、source/dcdflib.c/src と source/randlib.c/src にある C 言語のソースコードを利用します。

source/dcdflib.c/src, source/randlib.c/src 以下のファイルを stats-1.0.3 以下にコピーします。

$ cp -p source/dcdflib.c/src/* stats-1.0.3/
$ cp -p source/randlib.c/src/* stats-1.0.3/

あとは、PECL 拡張モジュールをビルドする際の通常の手順と同じです。以下のようになります。

$ cd stats-1.0.3
$ phpize
$ ./configure
$ make
$ sudo make install

ところが、以下のエラーメッセージが表示されて make に失敗しました。ftnstop 関数が dcdflib.c と randlib.c で重複して定義されているため、リンクに失敗しているようです。

.libs/randlib.o: In function `ftnstop':
/.../stats-1.0.3/randlib.c:2102: multiple definition of `ftnstop'
.libs/dcdflib.o:/.../stats-1.0.3/dcdflib.c:9082: first defined here
collect2: ld returned 1 exit status

この関数は randlib.c 内部でしか使われないようなので、以下のように関数名を変更してしまえば問題なくビルドできました*3

$ perl -i -p -e 's/ftnstop/ftnstop2/' randlib.c

これで無事に make install まで成功します。なお、make install すると /usr/lib64/php/modules*4 以下の stats.so ファイルが上書きされます。心配な場合は事前にバックアップするのがよいかもしれません。

最後に、バグが修正されていることを確認します。この記事の冒頭で作成したプログラムを改めて実行すると、今度はちゃんと結果が出力されました。

$ php bug_58394.php
245.5600000191
283.00000000117

*1:which = 2 の場合がこれに相当します。

*2:source ディレクトリは、DCDFLIB を展開したときに source/dcdflib.[cf] が、RANDLIB を展開したときに source/randlib.[cf] が、それぞれ作成されます。

*3:実は、stats-1.0.3 に含まれている randlib.c でも、このように修正されています。別の修正方法としては、static にしてファイルスコープにする、extern 宣言して関数本体を消してしまう (ftnstop 関数の実装は dcdflib.c と randlib.c で全く同じです) 等でもよいと思います。

*4:ディレクトリはシステムの設定に依存しますが。