PHPで2進数の循環小数かどうか判定する

(1/1)
有限小数、循環小数、分数、無理数」では、2進数の循環小数を紹介した。
コンピュータは内部演算を2進数で行っているが、10進数の小数を2進数に変換したとき、循環小数になるものが多い。たとえば、0.5,0.6,0.7は、すべて2進数の循環小数になる。
循環小数があると、10進数では演算誤差がなかったのに、コンピュータの計算結果は予期せぬ演算誤差を発生させることになる。
そこで今回は、入力された小数が2進数の循環小数かどうかを判定するプログラムを作ってみることにする。

目次

サンプル・プログラムの実行例

PHPで2進数の循環小数かどうか判定する
本プログラムを実行すると、10進数の小数の多くが、2進数では循環小数になることが分かる。演算誤差には注意しよう。

サンプル・プログラムのダウンロード

圧縮ファイルの内容
isCirculatingDecimal.phpサンプル・プログラム本体
pahooDataStructure.phpデータ構造に関わるクラス。
分数クラス pahooFrac の使い方は「PHPによる分数クラスの作成」を参照。include_path が通ったディレクトリに配置すること。

小数の2進数表記

まず10進数の小数を表記を分解してみよう――
 mimetex 
 mimetex 
 mimetex 

つぎに10進数の小数を2進数の小数に変換してみよう――
 mimetex 
 mimetex 
 mimetex 

つまり、2進数に変換したときに循環小数にならないケースは、通分して、分母が2のべき乗になるケースに限られる。

PHPで分数を扱うクラス pahooFrac は「PHPによる分数クラスの作成」で紹介したとおりだ。このクラスを使い、10進小数の分母を10のべき乗に設定し、それを約分して分母が2のべき乗にならなければ循環小数と判定する。

解説:初期値など

初期値などは、あらかじめ定数で定義しておく。
10進数の小数部を取り出し、対数計算などを行う関係上、ここで誤差が発生しないように扱える最小の小数桁数を MAX_DECIMAL として制限しておく。

0033: //入力小数(初期値)
0034: define('DEF_NUMBER', '1.05');
0035: 
0036: //扱える最小の小数桁数(10進数)
0037: define('MAX_DECIMAL', 8);
0038: 
0039: //データ構造クラス
0040: require_once('pahooDataStructure.php');

解説:2進数の循環小数かどうか判定する

前述の考え方をユーザー関数として実装したのが isCirculatingDecimal である。
前半は入力データを検査し、後半で2進数の循環小数かどうかを判定する。

0137: /**
0138:  * 2進数の循環小数かどうか判定する
0139:  * @param string $number判定する小数
0140:  * @param string $frac   分数を格納
0141:  * @param string $errmsgエラーメッセージ格納
0142:  * @return bool TRUE:循環小数である/FALSE:循環小数でないまたはエラー
0143: */
0144: function isCirculatingDecimal($number, &$frac, &$errmsg) {
0145:     $errmsg = '';
0146: 
0147:     //入力データ検査
0148:     if (preg_match('/^([\-|\+]*[0-9]*)\.*([0-9]*)/ui', $number$arr) == 0) {
0149:         $errmsg = '入力値が小数ではありません';
0150:         return FALSE;
0151:     }
0152: 
0153:     //小数部があるかどうか
0154:     if (! isset($arr[2]))    return FALSE;
0155: 
0156:     //入力データ検査
0157:     $dec = $arr[2];
0158:     if (strlen($dec) > MAX_DECIMAL) {
0159:         $errmsg = '入力できるのは小数第' . MAX_DECIMAL . '位までです';
0160:         return FALSE;
0161:     }
0162: 
0163:     //2進数の循環小数かどうか判定
0164:     $pf = new pahooFrac($decpow(10, strlen($dec)));    //分数クラス
0165:     $pf->reduce();                                        //約分
0166:     $frac = $pf->printFrac();
0167:     $res = log($pf->denominator, 2);      //分母が2のべき乗かどうか
0168:     $res = $res - floor($res);
0169:     $pf = NULL;
0170: 
0171:     return ($res != 0);
0172: }

参考サイト

(この項おわり)
header