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

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

(2024年1月27日)pahooInputData導入,入力バリデーション強化,TeX画像をMathJaxに置き換え

目次

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

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

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

圧縮ファイルの内容
isCirculatingDecimal.phpサンプル・プログラム本体
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooDataStructure.phpデータ構造に関わるクラス。
分数クラス pahooFrac の使い方は「PHPによる分数クラスの作成」を参照。include_path が通ったディレクトリに配置すること。
isCirculatingDecimal.php 更新履歴
バージョン 更新日 内容
1.1.0 2020/02/11 pahooInputData導入,入力バリデーション強化
1.0 2020/02/11 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加
pahooDataStructure.php 更新履歴
バージョン 更新日 内容
5.4 2022/09/26 list() → list3() メソッド名変更
5.3 2022/05/15 pahooHeapクラス追加
5.21 2018/08/25 サマータイム対応
5.2 2018/07/28 pahooAVLtreeクラス追加
5.1 2018/07/24 pahooBStreeクラス追加

小数の2進数表記

まず10進数の小数を表記を分解してみよう――
\( \begin{eqnarray}
0.5 &=& 5\times10^{-1} \\
0.25 &=& 2\times10^{-1} + 5\times10^{-2} \\
0.375 &=& 3\times10^{-1} + 7\times10^{-2} + 5\times10^{-3}
\end{eqnarray} \)

つぎに10進数の小数を2進数の小数に変換してみよう――
\( \begin{eqnarray}
0.5 &=& 1\times2^{-1} \\
0.25 &=& 0\times2^{-1} + 1\times2^{-2} \\
0.375 &=& 0\times2^{-1} + 1\times2^{-2} + 1\times2^{-3} \\
\end{eqnarray} \)

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

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

解説:初期値など

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

  55: //表示幅(ピクセル)
  56: define('WIDTH', 600);
  57: 
  58: //入力小数(初期値)
  59: define('DEF_NUMBER', '1.05');
  60: 
  61: //扱える最小の小数桁数(10進数)
  62: define('MAX_DECIMAL', 8);
  63: 
  64: //データ構造クラス
  65: require_once('pahooDataStructure.php');

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

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

 139: /**
 140:  * 2進数の循環小数かどうか判定する
 141:  * @param   string $number 判定する小数
 142:  * @param   string $frac   分数を格納
 143:  * @param   string $errmsg エラーメッセージ格納
 144:  * @return  bool TRUE:循環小数である/FALSE:循環小数でないまたはエラー
 145: */
 146: function isCirculatingDecimal($number, &$frac, &$errmsg) {
 147:     $errmsg = '';
 148: 
 149:     //入力データ検査
 150:     if (preg_match('/^([\-|\+]*[0-9]*)\.*([0-9]*)/ui', $number, $arr) == 0) {
 151:         $errmsg = '入力値が小数ではありません';
 152:         return FALSE;
 153:     }
 154: 
 155:     //小数部があるかどうか
 156:     if (! isset($arr[2]))   return FALSE;
 157: 
 158:     //入力データ検査
 159:     $dec = $arr[2];
 160:     if (strlen($dec> MAX_DECIMAL) {
 161:         $errmsg = '入力できるのは小数第' . MAX_DECIMAL . '位までです';
 162:         return FALSE;
 163:     }
 164: 
 165:     //2進数の循環小数かどうか判定
 166:     $pf = new pahooFrac($dec, pow(10, strlen($dec)));   //分数クラス
 167:     $pf->reduce();                                      //約分
 168:     $frac = $pf->printFrac();
 169:     $res = log($pf->denominator, 2);        //分母が2のべき乗かどうか
 170:     $res = $res - floor($res);
 171:     $pf = NULL;
 172: 
 173:     return ($res !0);
 174: }

参考サイト

(この項おわり)
header