May 13, 2016

php empty 関数ではまった

このエントリーは IT エンジニア向けです。

何の因果か、最近 php を書いている。 最後に php 書いたのいつだろう? 確か iPhone アプリのサーバーサイドを実装した時かな。 もう金輪際 php は使いたくないと思った日々が懐かしい。 まあ、最近は type hint もついたし、php も昔ほど嫌いじゃないけどね。

そんな中、久しぶりに php 書いたら若干ハマったことが合ったので書いてみる。

俺は Bitcoin に代表される暗号通貨関連の仕事をしている分けだが、残念な事に php では関連するライブラリがまだ少ない。 どっかのオープンソースから拝借しようかとも思ったが、php 製の有名なオープンソースも見つからなかった。 (探せば有るんだが、わざわざソースをハックする信頼性や価値が有るとは思えなかった。)

ってな分けで、俺の感覚ではどっかのライブラリ使いたくなるような下回りのメソッドも今回は自力で実装するハメに。 とはいえ、俺自身、暗号通貨についての知識はまだ浅いし中、下回りの実装は勉強にもなるので悪いことばかりではない。

半分イヤイヤ、半分ウキウキでライブラリの実装を行った。

ところが、このライブラリを使った環境で負荷検証でポチポチエラーが出てくる。 エラーの原因は負荷ではないっぽい。 プログラムのバグだ。

「エッヂケースかな?」と思い、色々な境界値を入れてテストしてみたが、正常に動く。 分からん。

至る所に var_dump を入れ print デバッグを行っていると、巨大整数の取り扱いでエラーが起こっているような気がしてきた。

ご存知の方も多いかもしれないが、php ではネイティブで巨大な整数が取り扱えない。 俺の環境では、整数は 64 bit の固定長だった。 64 bit ではとても足りなかったので自前で巨大整数を扱う関数を作ったのだ。 そこでエラーが発生しているらしい。

ただ、正直なところ最初は信じられなかった。

ここでエラーが起きると後で面倒な事は分かりきっていたので、テストも少し入念に行った。 一方、これらの機能は副作用が無いのでテストはどれも簡単だ。 それなのに、本当にここでエラーが起きるのか?

半信半疑で再度色々とやってみると、驚きの結果が。

なんと、48 という数字を用いた時だけ四則演算(足し算、引き算、掛け算、割り算)の結果がおかしくなっていたのだ。 何でよりによって 48 ? 全部エラーなら分かる。 0, 1, 255, 256 というコンピューター的に特別な値だけでエラーが起こるのも、分かる。 でも何で 48 ?

完全に混乱しながらデバッグを続けて、やっとわかった。

php 組み込みの empty 関数だ。

empty はデータが空かどうかを判断する。 例えば、空の array や空の string を渡すと TRUE を返し、それ以外の場合は FALSE を返す。

と、思っていた。

だが実際には、少し例外が合ったようだ。

'0' という文字列 (数字の 0 を表す文字列) を渡した時も empty は TRUE を返す。

俺は大きい整数を扱うために整数を byte 列に変換し、string として保存していた。 そして、その string が空の場合は数字の 0 として扱うよう、empty を使って文字列をチェックしていた。 ところが 48 という数字を byte 列にすると php が文字列の '0' と解釈し、empty が俺の意図に反する挙動を示したのだ。 ('0' の ascii コードは 48)

つまり、48 という数字が所々で 0 に変換されていたため、計算結果がおかしくなっていた。php プログラマの間では常識なんだろうけど、知らなかった。

php の型は超弱い事は知っていた。だからこそハマるとしたらこういう所だと思って注意していたのに、それでも落とし穴を踏みぬいてしまった。なんか悔しい。