読者です 読者をやめる 読者になる 読者になる

俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

日報 #18 - たまにはPHPのソースコードを読みましょう

18回目だそうです。
毎回ネタもないのに、「ただ続けろ」という俺からの命令を俺が守り続けてきましたが、
今日ほどもう限界じゃないかと感じたことはないですね。

えっ、やるんですか?
やるんです。

諸事情あって昔のコードを少しいじっておりましたところ、 すこし見慣れないコードがあったので、それについてちょっと。

ちょっとしたヘルパーみたいな関数なのですが、
コメントアウトにはこう書いてありました。

/**
 * BinaryArray
 * 10進数を与えると、2進数の各桁が配列として返されます。
 * 例)
 * BinaryArray(10) = Array([0]=>0,[1]=>1,[2]=>0,[3]=>1)
 * 10は、2進数で1010
 * 
 * 配列のキーが桁番号と対応します。
 * @param unknown_type $decimal

 */
function BinaryArray($decimal) { … }

要は、10進数を食わせると、2進数になって返ってくると。

コレ実はすごい簡単で、PHPにはdecbinという関数( http://php.net/manual/ja/function.decbin.php )が既に存在しています。これを文字ごとに切って配列に格納すれば、それで事足りるわけですね。
でもそれを何故か実装しています。不思議ですが、当時はdecbinを知らなかったのか、または「もちろん調べたけど、車輪ってたまに再発明してみたくなるんだよね」という癖が発動したのかのどちらかでしょう。
さて、どうやって実装したのか、見てみます。

function BinaryArray($decimal){
     $binary = "";
     $digit = 0;
     $result = array();
    
     if(!is_integer($decimal) || $decimal < 0 )
          return null;
    
     $digit = floor(log($decimal,2))+1;
    
     for($i = 0 ; $i < $digit ; $i++)
          $result[$i] = $decimal&(1<<$i) ? 1:0;
    
     return $result;
}

それなりに簡素です。
ですが、一見して「何してんだコレ、意味不明だな」と思ったので、コレが今日のテーマです。

自分で書いたコードです。きっと分かるはず。
見慣れない部分は

$result[$i] = $decimal&(1<<$i) ? 1:0;

これですね。シフト演算<<をした上に、&演算子。要はビット計算をしているようです。
興味対象の変数に対して、1->10->100と桁を順次ずらした2進数をビット積し続け、
その都度の値がゼロなら0、ゼロ意外なら1という用に進数を取得しているようです。
てっきり「2で割ってあまりを…」みたいな作業かと思っていたら、
なかなかどうして、自分にしては結構工夫したんじゃないでしょうか。

ということで答え合わせをしましょう。
decbinのソースコードを読んでみます。
まずは皆大好きphp-srcへ(https://github.com/php/php-src
おもむろにdecbinと検索すると、/ext/standard/math.cの中にPHP_FUNCTIONが書いてあるようです。
中身をみてみると丁度こんな感じ。

/* {{{ proto string decbin(int decimal_number)
   Returns a string containing a binary representation of the number */
PHP_FUNCTION(decbin)
{
     zval **arg;
     char *result;

     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) {
          return;
     }
     convert_to_long_ex(arg);
     result = _php_math_longtobase(*arg, 2);
     RETURN_STRING(result, 0);
}
/* }}} */

どうやら実体は_php_math_longtobase(*arg,2)だそうです。
同じファイルにあります。

/* {{{ _php_math_longtobase */
/*
 * Convert a long to a string containing a base(2-36) representation of
 * the number.
 */
PHPAPI char * _php_math_longtobase(zval *arg, int base)
{
     static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
     char buf[(sizeof(unsigned long) << 3) + 1];
     char *ptr, *end;
     unsigned long value;

     if (Z_TYPE_P(arg) != IS_LONG || base < 2 || base > 36) {
          return STR_EMPTY_ALLOC();
     }

     value = Z_LVAL_P(arg);

     end = ptr = buf + sizeof(buf) - 1;
     *ptr = '\0';

     do {
          *--ptr = digits[value % base];
          value /= base;
     } while (ptr > buf && value);

     return estrndup(ptr, end - ptr);
}
/* }}} */

baseはlog計算の底のことで、意外にもdigitsなる36進数対応の文字列を用意して、
ずっと割り算してあまりをとってくる方法を採用しているようです。
えーー
俺のやり方のほうが早そうじゃねぇ??
って思ってニンマリしたのが今日のハイライト。