DEVELOPERS BLOGデベロッパーズブログ
- HOME >
- 加藤 正人のデベロッパーズブログ >
- 分数電卓を作ってみる・その1
加藤 正人のデベロッパーズブログ
- 氏名
- 加藤 正人
- 役職
- 多分SE
- 血液型
- 秘密
- 出没
- 美味しいもののあるところ
- 特色
- タヒチ大好き。ちょいメタボ。
- 2020/12/15
- CakePHP3 のリダイレクトがうまく行かない例
- 2020/11/12
- Apache Bench
- 2020/10/05
- PhpSpreadsheet でExcel ファイルを読み込む (CakePHP3)
最近子供の宿題で、分数の計算をする機会が増えた。
正解例はもらえないので、答え合わせをするにも50問も100問もあると親が実際に計算をすることになり少々 (かなり) 大変だ。
ということで、簡単な分数電卓を CakePHP で Web ページ上に実装してみた。(実装例はこちら。)
基本構想
- 電卓ページの実装はフォームのテキスト入力欄に分数と四則演算を入力し、ボタンクリックで結果を表示する。
- 分数の表記は入力の容易さと構文解析の容易さのバランスを取り、帯分数 $$1 \frac{\;2\;}{\;3\;}$$ (1と3分の2、古い読み方なら 1荷(か)3分の2) なら [1&2/3]、帯分数でない分数 $$\frac{\;17\;}{\;23\;}$$ (23分の17) なら [17/23] のようにする。
- 結果は「テキスト」ではなく、MathJax を使用して数式として見やすい形式で出力する。
必要なもの
分数電卓を実装するためには、以下のものが必要となる。
- 分数のデータの保持および演算機能を提供するクラス (Fraction クラス)
- 入力した分数式を解釈し演算順序を判定するための構文パーサ
今回は Fraction クラスについて解説する。
PHP には分数に対応する型は存在していないので、クラスでデータ保持構造と演算機能を実現する。クラス構造としては、分母と分子だけからなる BaseFraction クラスをまず定義する。これは、分子・分母の2つの要素だけで演算することにより四則演算の実装が容易なためだ。そして BaseFraction クラスを拡張する形で帯分数に対応した Fraction クラスを実装する。
BaseFraction クラス
まずはコア機能を実装するための BaseFraction の説明から。
001 | class BaseFraction |
002 | { |
003 | // 真分数または仮分数を実装するための基本クラス。帯分数はこのクラスを拡張した Fraction クラスで実装する。 |
004 | // 分子・分母は GMP 数で実装するので、サーバ側 PHP 環境に GMP (GNU Multiple Precision) が実装されていることが前提となる。 |
005 | // 表示用に TeX コマンド表現への変換をサポートする。 |
006 | const StringFracFormat = '[%s/%s]' ; // 分数の文字表現 |
007 | const TeXFracFormat = '\frac{\;%s\;}{\;%s\;}' ; // 分数の TeX 書式 (分数の横棒が長くなるように調整済み) |
008 |
009 | protected $sign = 0; // 符号 (1: 正数; 0: 0; -1: 負数) |
010 | protected $numerator = NULL; // 帯分数の分子 (Numerator) |
011 | protected $denominator = NULL; // 帯分数の分母 (Denominator) |
012 |
013 | protected $e_divided_by_zero = 'Divided by 0 (zero).' ; |
014 |
015 | // 分数オブジェクトを生成する (分子・分母は与えられたままの値を使用する。明示的に行わない限り約分はされない。) |
016 | public function __construct( $num , $den =1) |
017 | { |
018 | if ( is_numeric ( $num ) ) $num = gmp_init( $num ); |
019 | if ( is_numeric ( $den ) ) $den = gmp_init( $den ); |
020 |
021 | $this ->numerator = gmp_abs( $num ); |
022 | if ( is_null ( $den ) ) |
023 | { |
024 | // 分母が NULL ⇒ 整数を表す |
025 | $this ->denominator = NULL; |
026 | $this ->sign = gmp_sign( $num ); |
027 | } |
028 | else |
029 | { |
030 | if ( $den === 0 || gmp_strval( $den ) == '0' ) |
031 | { |
032 | throw new Exception( $this ->e_divided_by_zero); |
033 | } |
034 | // 分母が NULL でない ⇒ 分数を表す |
035 | $this ->denominator = gmp_abs( $den ); |
036 | $this ->sign = gmp_sign( $num ) * gmp_sign( $den ); |
037 | } |
038 | } |
039 |
040 | // 分数オブジェクトのコピーを作成する |
041 | protected function copy () |
042 | { |
043 | return new BaseFraction(gmp_strval( $this ->numerator)* $this ->sign, $this ->denominator); |
044 | } |
045 |
046 | // 分数の符号を取得する (整数値; 1: 正; 0: ゼロ; -1: 負) |
047 | public function getSign() |
048 | { |
049 | return $this ->sign; |
050 | } |
051 |
052 | // 分子を取得する (GMP 数) |
053 | public function getDenominator() |
054 | { |
055 | return $this ->denominator; |
056 | } |
057 |
058 | // 分母を取得する (GMP 数) |
059 | public function getNumerator() |
060 | { |
061 | return $this ->numerator; |
062 | } |
063 |
064 | // 分数を約分し自オブジェクトを返す |
065 | public function reduction() |
066 | { |
067 | if ( ! is_null ( $this ->denominator) ) |
068 | { |
069 | // 分母が NULL でない場合のみ約分処理を実行 |
070 | $gcd = gmp_gcd( $this ->numerator, $this ->denominator); |
071 | $this ->numerator = gmp_div_q( $this ->numerator, $gcd ); |
072 | $this ->denominator = gmp_div_q( $this ->denominator, $gcd ); |
073 | } |
074 | return $this ; |
075 | } |
076 |
077 | /////////////////////////////// |
078 | // 分数の表現 |
079 | /////////////////////////////// |
080 | private function __display( $format ) |
081 | { |
082 | if ( $this ->sign === 0 ) |
083 | { |
084 | // 符号情報が 0 ⇒ 0 を表示 |
085 | $ret = '0' ; |
086 | } |
087 | if ( is_null ( $this ->denominator) ) |
088 | { |
089 | // 分母が NULL ⇒ 整数として表示 |
090 | if ( $this ->sign > 0 ) |
091 | { |
092 | $ret = "{$this->numerator}" ; |
093 | } |
094 | else |
095 | { |
096 | $ret = "-{$this->numerator}" ; |
097 | } |
098 | |
099 | } |
100 | else |
101 | { |
102 | // 分母が NULL でない ⇒ 分数表示 |
103 | if ( $this ->sign > 0 ) |
104 | { |
105 | $ret = sprintf( $format , gmp_strval( $this ->numerator), gmp_strval( $this ->denominator)); |
106 | } |
107 | else |
108 | { |
109 | $ret = sprintf( '-' . $format , gmp_strval( $this ->numerator), gmp_strval( $this ->denominator)); |
110 | } |
111 | } |
112 | return $ret ; |
113 | } |
114 |
115 | public function toTeX() |
116 | { |
117 | return $this ->__display(self::TeXFracFormat); |
118 | } |
119 |
120 | public function toString() |
121 | { |
122 | return $this ->__display(self::StringFracFormat); |
123 | } |
124 |
125 | /////////////////////////////// |
126 | // 算術演算 |
127 | /////////////////////////////// |
128 |
129 | // 自オブジェクトに分数を1つ加算し、約分した結果を新しいオブジェクトとして返す。 |
130 | public function add( $f ) |
131 | { |
132 | if ( $this ->sign === 0 ) |
133 | { |
134 | // 自分自身が 0 ⇒ 加算値を複製して返す |
135 | $ret = $f -> copy ()->reduction(); |
136 | } |
137 | else if ( $f ->getSign() == 0 ) |
138 | { |
139 | // 加算値側が 0 ⇒ 自分自身を複製して返す |
140 | $ret = $this -> copy ()->reduction(); |
141 | } |
142 | else |
143 | { |
144 | // どちらも 0 でない |
145 | $num = gmp_add( |
146 | gmp_mul(gmp_mul( $this ->numerator, $this ->sign), $f ->denominator), |
147 | gmp_mul( $this ->denominator, gmp_mul( $f ->numerator, $f ->sign)) |
148 | ); |
149 | $den = gmp_mul( $this ->denominator, $f ->denominator); |
150 |
151 | $ret = new BaseFraction( $num , $den ); |
152 | } |
153 | return $ret ->reduction(); |
154 | } |
155 |
156 | // 自オブジェクトに分数を1つ乗算し、約分した結果を新しいオブジェクトとして返す。 |
157 | public function mul( $f ) |
158 | { |
159 | if ( $this ->sign === 0 || $f ->sign === 0 ) |
160 | { |
161 | // 自オブジェクトと乗数のうち少なくとも一方が 0 だった ⇒ 新たに 0 オブジェクトを作成し返す |
162 | $ret = new BaseFraction(0); |
163 | } |
164 | else |
165 | { |
166 | $num = gmp_mul(gmp_mul( $this ->sign, $this ->numerator), gmp_mul( $f ->sign, $f ->numerator)); |
167 | $den = gmp_mul( $this ->denominator, $f ->denominator); |
168 | $ret = new BaseFraction( $num , $den ); |
169 | } |
170 |
171 | return $ret ->reduction(); |
172 | } |
173 |
174 | // 自オブジェクトの逆数を新しいオブジェクトとして返す。(約分はしない) |
175 | public function invert() |
176 | { |
177 | $sign = $this ->sign; |
178 | $num = $this ->numerator; |
179 | $den = $this ->denominator; |
180 | if ( gmp_cmp( $num , '0' ) == 0 ) |
181 | { |
182 | throw new Exception( $this ->e_divided_by_zero); |
183 | } |
184 | return new BaseFraction(gmp_mul( $sign , $den ), $num ); |
185 | } |
186 |
187 | // 自オブジェクトのコピーを新たに生成し、符号を反転して返す |
188 | public function neg() |
189 | { |
190 | $ret = $this -> copy (); |
191 | $ret ->sign *= -1; |
192 | return $ret ; |
193 | } |
194 |
195 | public static function is_base_fraction( $o ) |
196 | { |
197 | $class_name = @get_class( $o ); |
198 | return ( $class_name === __CLASS__ ); |
199 | } |
200 | } |
主なプロパティ (メンバー変数)
- protected $sign
-
分数全体の符号を表す整数値をとる。-1 ⇒ 負、0 ⇒ ゼロ、1 ⇒ 正。「分数が 0 (ゼロ) である」とは、分子 (および分母) の値の如何に関わらず「$sign が 0 であること」である。
- protected $numerator
-
分子。値は GMP (Gnu Multiple Precision) 数で実装することで、多倍長整数を扱う。
- protected $denominator
-
分母。値は GMP 数で実装し、多倍長整数を扱う。なお 0 (ゼロ) を指定した場合は例外 Exception をスローする。
主なメソッド
- public function __construct($num, $den=1)
-
コンストラクタ。分母($den) を省略した場合は 1。なお、与えられた分母と分子は約分せずそのまま設定する。約分が必要な場合は、パブリックメソッド reduction() を使用する。
- protected function copy()
-
当該オブジェクトのコピーを作成して返す。
- public function getSign()
-
BaseFraction の符号情報 (プロパティ $sign の値) を返す。
- public function getDenominator()
-
分子の値 (GMP 数) を取得する。
- public function getNumerator()
-
分母の値 (GMP 数) を取得する。
- public function reduction()
-
分数を約分し、当該オブジェクト自身を返す。
- public function add($f)
-
引数 $f で与えられる BaseFraction オブジェクトを、当該オブジェクトに加算する。結果は既約分数。
- public function mul($f)
-
引数 $f で与えられる BaseFraction オブジェクトを、当該オブジェクトに掛ける。結果は既約分数。
- public function invert()
-
当該オブジェクトの逆数を BaseFraction オブジェクトとして返す。なお、当該オブジェクトの分子が 0 の場合は例外 Exception を発生する。
- public function neg()
-
当該オブジェクトのコピーを作成し、符号を反転して返す。
- public static function is_base_fraction($o)
-
引数 $o が BaseFraction のインスタンスであるかどうかを調べる。
Fraction クラス
続いて BaseFraction を拡張した Fraction を定義する。
001 | class Fraction extends BaseFraction |
002 | { |
003 | const StringFracRegex = '/^(-)?\[([0-9]*&)?([0-9]+)\/([0-9]+)\]$/' ; // 帯分数のテキスト表現読み込み用正規表現 |
004 |
005 | // 与えられた引数が GMP リソース ( |
006 | private function is_GMP( $obj ) |
007 | { |
008 | // GMP 数は PHP 5.6 より前は「リソース」。5.6 以降は GMP オブジェクト (実装による)。 |
009 | return ( is_resource ( $obj ) && get_resource_type( $obj ) == 'GMP integer' ) || ( is_object ( $obj ) && (get_class( $obj ) == 'GMP' )); |
010 | } |
011 |
012 | public function __construct( $int , $num =0, $den =1, $sign =NULL) |
013 | { |
014 | if ( is_string ( $int ) && preg_match(self::StringFracRegex, $int , $match ) ) |
015 | { |
016 | //debug(compact('match')); |
017 | // TODO (テキスト表現 |
018 | $sign = 1; |
019 | if ( $match [1] === '-' ) |
020 | { |
021 | $sign = -1; |
022 | } |
023 | $int = rtrim( $match [2], '&' ); |
024 | $num = $match [3]; |
025 | $den = $match [4]; |
026 | self::__construct( $int , $num , $den , $sign ); |
027 | } |
028 | else if ( is_numeric ( $int ) ) |
029 | { |
030 | $int = gmp_init( $int ); |
031 | if ( is_numeric ( $num ) ) |
032 | { |
033 | $num = gmp_init( $num ); |
034 | if ( is_numeric ( $den ) ) |
035 | { |
036 | $den = gmp_init( $den ); |
037 | if ( ! is_numeric ( $sign ) && ! is_null ( $sign ) ) |
038 | { |
039 | throw new Exception( 'The fourth parameter for ' . __METHOD__ . ' MUST be an integer or numeric string.' ); |
040 | } |
041 | } |
042 | else if ( ! $this ->is_GMP( $den ) ) |
043 | { |
044 | throw new Exception( 'The third parameter for ' . __METHOD__ . ' MUST be an integer, numeric string or GMP number.' ); |
045 | } |
046 | } |
047 | else if ( ! $this ->is_GMP( $num ) ) |
048 | { |
049 | throw new Exception( 'The second parameter for ' . __METHOD__ . ' MUST be an integer, numeric string or GMP number.' ); |
050 | } |
051 | } |
052 | else if ( Fraction::is_fraction( $int ) ) |
053 | { |
054 | // 第1引数が分数 ⇒ 新しい分数オブジェクトを生成して返す |
055 | self::__construct(0, $int ->numerator, $int ->denominator, $int ->sign); |
056 | return ; |
057 | } |
058 | else if ( ! $this ->is_GMP( $int ) ) |
059 | { |
060 | throw new Exception( 'The first parameter for ' . __METHOD__ . ' MUST be an integer, numeric string or GMP number.' ); |
061 | } |
062 |
063 | if ( is_string ( $int ) && preg_match(self::StringFracRegex, $int , $match ) ) |
064 | { |
065 | debug(compact( 'match' )); |
066 | // TODO (テキスト表現 |
067 | $sign = 1; |
068 | if ( $match [1] === '-' ) |
069 | { |
070 | $sign = -1; |
071 | } |
072 | $int = rtrim( $match [2], '&' ); |
073 | $num = $match [3]; |
074 | $den = $match [4]; |
075 | self::__construct( $int , $num , $den , $sign ); |
076 | } |
077 |
078 | if ( is_null ( $den ) ) |
079 | { |
080 | if ( is_null ( $num ) ) |
081 | { |
082 | // 分母・分子ともに NULL ⇒ 整数 |
083 | parent::__construct( $int ); |
084 | } |
085 | else |
086 | { |
087 | throw new Exception( '3rd argument for ' . __METHOD__ . ' cannot be a NULL.' ); |
088 | } |
089 | } |
090 | else |
091 | { |
092 | if ( is_null ( $sign ) ) |
093 | { |
094 | parent::__construct(gmp_add(gmp_mul( $int , $den ), $num ), $den ); // 帯分数は仮分数として保存する |
095 | } |
096 | else if ( $sign === 1 || $sign === 0 || $sign === -1 ) |
097 | { |
098 | parent::__construct(gmp_mul( $sign , gmp_add(gmp_mul( $int , $den ), $num )), $den ); // 帯分数は仮分数として保存する |
099 | } |
100 | } |
101 | } |
102 |
103 | // 分数のデータアクセスメソッドは、Fraction で拡張した部分だけを定義すればOK。 |
104 |
105 | // 整数部を取得する |
106 | public function getIntegralPart() |
107 | { |
108 | if ( is_null ( $this ->denominator) ) |
109 | { |
110 | // 分母が NULL ⇒ 整数 (分子) |
111 | $ret = $this ->numerator; |
112 | } |
113 | else |
114 | { |
115 | // 分母が NULL でない ⇒ 分子を分母で割った商 |
116 | $ret = gmp_div_q( $this ->numerator, $this ->denominator); |
117 | } |
118 | return $ret ; |
119 | } |
120 |
121 | // 分子を取得する (GMP 数) |
122 | public function getNumerator( $raw =FALSE) |
123 | { |
124 | if ( is_null ( $this ->denominator) || $raw ) |
125 | { |
126 | // 分母が NULL の場合、および生データ指定 (=帯分数化しない) がある場合 |
127 | $ret = $this ->numerator; // 分子そのもの |
128 | } |
129 | else |
130 | { |
131 | $ret = gmp_mod( $this ->numerator, $this ->denominator); // 分子を分母で割った剰余を返す |
132 | } |
133 | return $ret ; |
134 | } |
135 |
136 | /**** |
137 | // 分母を取得する (GMP 数) |
138 | public function getDenominator() |
139 | { |
140 | return $this->denominator; |
141 | } |
142 |
143 | // 分数の符号を取得する (整数 1: 正数; 0: ゼロ; -1: 負数 |
144 | public function getSign() |
145 | { |
146 | return $this->sign; |
147 | } |
148 | // 分数を通分する |
149 | public function reduction() |
150 | { |
151 | return parent::reduction(); |
152 | } |
153 | ****/ |
154 |
155 | ///////////////////////////////////// |
156 | // 表示用メソッド群 |
157 | ///////////////////////////////////// |
158 |
159 | const StringFracFormat = '[%s&%s/%s]' ; // 帯分数の文字表現 |
160 | const TeXFracFormat = '%s\frac{\;%s\;}{\;%s\;}' ; // 帯分数の TeX 書式 (分数の横棒が長くなるように調整済み) |
161 | // 分数を読み戻し可能なテキスト表記にする |
162 | public function __display( $format1 , $format2 , $mixform =TRUE) |
163 | { |
164 | if ( $mixform ) |
165 | { |
166 | // 帯分数表示 |
167 | $int = $this ->getIntegralPart(); |
168 | $num = $this ->getNumerator(); |
169 | $den = $this ->denominator; |
170 | $sign = $this ->sign; |
171 |
172 | if ( gmp_cmp( $den , '1' ) == 0 ) |
173 | { |
174 | // 分母が1 ⇒ 仮分数の分子に符号をつけてそのまま整数 |
175 | $ret = gmp_strval(gmp_mul( $this ->numerator, $sign )); |
176 | } |
177 | else |
178 | { |
179 | // 分母がNULL でない ⇒ 分数表示 |
180 | if ( $sign == 0 ) |
181 | { |
182 | // 0 |
183 | $ret = '0' ; |
184 | } |
185 | else if ( $sign > 0 ) |
186 | { |
187 | // 正 |
188 | if ( gmp_cmp( $int ,0) == 0 ) |
189 | { |
190 | $ret = sprintf( $format1 , gmp_strval( $num ), gmp_strval( $den )); |
191 | } |
192 | else |
193 | { |
194 | $ret = sprintf( $format2 , gmp_strval( $int ), gmp_strval( $num ), gmp_strval( $den )); |
195 | } |
196 | } |
197 | else |
198 | { |
199 | // 負 |
200 | if ( gmp_cmp( $int ,0) == 0 ) |
201 | { |
202 | $ret = sprintf( '-' . $format1 , gmp_strval( $num ), gmp_strval( $den )); |
203 | } |
204 | else |
205 | { |
206 | $ret = sprintf( '-' . $format2 , gmp_strval( $int ), gmp_strval( $num ), gmp_strval( $den )); |
207 | } |
208 | } |
209 | } |
210 | } |
211 | else |
212 | { |
213 | // 純分数表示 ⇒ 親クラスの toTeX() で処理 |
214 | $ret = parent::toTeX(); |
215 | } |
216 | return $ret ; |
217 | } |
218 |
219 | public function toString( $mixform =TRUE) |
220 | { |
221 | return $this ->__display(parent::StringFracFormat, self::StringFracFormat, $mixform ); |
222 | } |
223 |
224 | // 分数を TeX 表記文字列に変換する |
225 | public function toTeX( $mixform =TRUE) |
226 | { |
227 | return $this ->__display(parent::TeXFracFormat, self::TeXFracFormat, $mixform ); |
228 | } |
229 |
230 | // TeX 文字列を GoogleCharts の Math で画像化する img タグを生成する |
231 | public static function googleTeX( $tex ) |
232 | { |
233 | return sprintf( '<img src="https://chart.googleapis.com/chart?cht=tx&chl=%s" />' , urlencode( $tex )); |
234 | } |
235 |
236 | // TeX 文字列を MathJax コマンド文字列に変換する |
237 | public static function MathJax( $tex , $inline =TRUE) |
238 | { |
239 | if ( $inline ) |
240 | { |
241 | $delimiter = '$' ; // inline 用デリミタ |
242 | } |
243 | else |
244 | { |
245 | $delimiter = '$$' ; // display 用デリミタ |
246 | } |
247 | return $delimiter . $tex . $delimiter ; |
248 | } |
249 |
250 | // 型判定 |
251 | public static function is_fraction( $o , $strict =FALSE) |
252 | { |
253 | $class_name = @get_class( $o ); |
254 | if ( $strict ) |
255 | { |
256 | $ret = ( $class_name === __CLASS__ || parent::is_base_fraction( $o )) && ( $o ->getNumerator() != 0); |
257 | } |
258 | else |
259 | { |
260 | $ret = ( $class_name === __CLASS__ ||parent::is_base_fraction( $o )); |
261 | } |
262 | return $ret ; |
263 | } |
264 |
265 | public function is_zero() |
266 | { |
267 | return ( $this ->sign === 0); |
268 | } |
269 |
270 | public function is_positive() |
271 | { |
272 | return ( $this ->sign > 0); |
273 | } |
274 |
275 | public function is_negative() |
276 | { |
277 | return ( $this ->sign < 0); |
278 | } |
279 |
280 | ////////////////////////////////////////////// |
281 | // 算術演算 |
282 | ////////////////////////////////////////////// |
283 |
284 | public function add( $f ) |
285 | { |
286 | if ( !self::is_fraction( $f ) ) |
287 | { |
288 | if ( is_string ( $f ) && preg_match( '/^[+-]?[0-9]+$/' , $f ) ) |
289 | { |
290 | $f = new Fraction( $f ); |
291 | } |
292 | else if ( is_numeric ( $f ) ) |
293 | { |
294 | $f = $f + 0; |
295 | if ( is_integer ( $f ) ) |
296 | { |
297 | $f = new Fraction( $f ); |
298 | } |
299 | else if ( is_float ( $f ) ) |
300 | { |
301 | $s = sprintf( '%f' , $f ); |
302 | if ( ( $pos = strpos ( $s , '.' )) === FALSE ) |
303 | { |
304 | // 小数点が見つからなかった |
305 | $f = new Fraction( $f ); |
306 | } |
307 | else |
308 | { |
309 | // 小数点が見つかった |
310 | list( $a , $b ) = explode ( '.' , $s ); // $a は符号を含むことがある |
311 | $num = $a . $b ; |
312 | $den = '1' . str_repeat ( '0' , strlen ( $b )); |
313 | $f = new Fraction(0, $num , $den ); |
314 | } |
315 | } |
316 | } |
317 | else |
318 | { |
319 | throw new Exception( 'invalid argument given for ' . __METHOD__ . ' ' . print_r( $f , TRUE)); |
320 | } |
321 | } |
322 | $base_fraction = parent::add( $f ); |
323 | $base_fraction = $base_fraction ->reduction(); // 結果は Fraction ではなく BaseFraction なので、帯分数形式にするには Fraction に直す。 |
324 | |
325 | $ret = new Fraction(0, $base_fraction ->numerator, $base_fraction ->denominator, $base_fraction ->sign); |
326 |
327 | return $ret ->reduction(); |
328 | } |
329 |
330 | public function mul( $f ) |
331 | { |
332 | if ( !self::is_fraction( $f ) ) |
333 | { |
334 | if ( is_string ( $f ) && preg_match( '/^[+-]?[0-9]+$/' , $f ) ) |
335 | { |
336 | $f = new Fraction( $f ); |
337 | } |
338 | else if ( is_numeric ( $f ) ) |
339 | { |
340 | $f = $f + 0; |
341 | if ( is_integer ( $f ) ) |
342 | { |
343 | $f = new Fraction( $f ); |
344 | } |
345 | else if ( is_float ( $f ) ) |
346 | { |
347 | $s = sprintf( '%f' , $f ); |
348 | if ( ( $pos = strpos ( $s , '.' )) === FALSE ) |
349 | { |
350 | // 小数点が見つからなかった |
351 | $f = new Fraction( $f ); |
352 | } |
353 | else |
354 | { |
355 | // 小数点が見つかった |
356 | list( $a , $b ) = explode ( '.' , $s ); // $a は符号を含むことがある |
357 | $num = $a . $b ; |
358 | $den = '1' . str_repeat ( '0' , strlen ( $b )); |
359 | $f = new Fraction(0, $num , $den ); |
360 | } |
361 | } |
362 | } |
363 | else |
364 | { |
365 | throw new Exception( 'invalid argument given for ' . __METHOD__ . ' ' . print_r( $f , TRUE)); |
366 | } |
367 | } |
368 | $base_fraction = parent::mul( $f ); |
369 | $base_fraction = $base_fraction ->reduction(); // 結果は Fraction ではなく BaseFraction なので、帯分数形式にするには Fraction に直す。 |
370 | |
371 | $ret = new Fraction(0, $base_fraction ->numerator, $base_fraction ->denominator, $base_fraction ->sign); |
372 |
373 | return $ret ->reduction(); |
374 | } |
375 |
376 | public function neg() |
377 | { |
378 | $ret = $this -> copy (); |
379 | if ( parent::is_base_fraction( $ret ) ) |
380 | { |
381 | $ret = new Fraction( $ret ); |
382 | } |
383 | $ret ->sign *= -1; |
384 | return $ret ; |
385 | } |
386 | } |
主なプロパティ
Fraction クラスでは、プロパティは定義しない。親クラス BaseFraction のプロパティを継承。
主なメソッド
- public function __construct($int, $num=0, $den=1, $sign=NULL)
-
コンストラクタ。$int は帯分数の整数部、$num は分子、$den は分母、$sign は分数全体の符号を指定する。
なお、実際には帯分数を仮分数に変換し、その仮分数で BaseFraction オブジェクトを生成する。
- public function getIntegralPart()
-
Fraction オブジェクトの整数部分を返す。実際には整数部はデータとしては保持していないので、分子と分母から毎回計算する。
- public function getNumerator($raw=FALSE)
-
分子を取得する。パラメータ $raw が TRUE の場合は仮分数の分子を、そうでない場合は帯分数化した場合の分子を計算して返す。
- public static function is_fraction($o, $strict=FALSE)
-
与えられたオブジェクト $o が分数かどうかを判定する。
第2引数 $strict が FALSE (既定値) の場合、分子が 0 の場合は「整数」とみなし分数とはしない。
- public function is_zero()
-
当該分数オブジェクトが 0 (ゼロ) かどうかを判定する。
- public function is_positive()
-
当該分数オブジェクトが正かどうかを判定する。
- public function is_negative()
-
当該分数オブジェクトが負かどうかを判定する。
- public function add($f)
-
引数 $f を当該オブジェクトに加算する。結果は既約分数。
- public function mul($f)
-
引数 $f を当該オブジェクトに掛ける。結果は既約分数。
- public function neg()
-
当該オブジェクトの複製を作成し、符号を反転させたものを返す。
次回は分数演算の式パーサを解説する予定。
関連エントリー
- 2018/04/14
- CakePHP のレンダリング結果を保存したい
- 2017/09/06
- CakePHP 2.x の Cookie と js.cookie.js
- 2017/07/08
- 時刻入力用 jQuery Plugin TimePicki の不具合調整
- 2017/06/18
- CakePHP プラグインで HTTPS 判定
- 2016/02/27
- 作業用モデルビヘイビア