数値の文字列を適切な数値クラスにパース（実数クラスの包括的取り扱い）

他の記事中で用いた小技だが、独立した記事にしたほうが良さそうなので作成した。

## 問
与えられた数値の文字列が、 `Integer` 形式（整数）なら `Integer` に、 `Rational` 形式（`"整数/自然数"`）なら `Rational` に、 `Float` 形式（小数点や指数表記）なら `Float` に変換せよ。

```ruby
func("+1")  #=> 1
func("2/3") #=> (2/3)
func("4.5") #=> 4.5
func("6e7") #=> 60000000.0
```

なお、上記に無い形式や、そもそも変換できない文字列に対する動作は好きに決めてよい。

## 答
### 面倒な方法
「それぞれのクラスに特徴的な文字」が文字列に含まれているか調べて、パースする数値クラスを決める。

```ruby
def func(str)
	case str
	when %r{[/]}   then str.to_r   # Rational
	when %r{[.eE]} then str.to_f   # Float
	else                str.to_i   # Integer
	end
end
```

### 素直な方法
モジュール関数 [`Kernel.#Integer`](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Integer.html) などは、文字列をパースできなければ例外を出す。それをキャッチして徐々に複雑な形式でパースを試せばいい。

```ruby
def func(str)
	Integer(str) rescue Float(str) rescue Rational(str)
end
```

[`Kernel.#Rational`](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Rational.html) は小数を分数に直せてしまうので、 [`Kernel.#Float`](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Float.html) の後にしている。

```ruby
Rational("4.5")   #=> (9/2)
Rational("4.5/3") #=> (3/2)
```

ちなみに `Integer` は二進・八進・十六進数表記も対応できる。 `Float` は十六進整数に対応できる。

### 楽な方法
[`Kernel.#Complex`](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Complex.html) で一旦複素数クラスに変換し、実部を取り出す。

```ruby
def func(str)
	Complex(str).real
end
```

[`String#to_c`](https://docs.ruby-lang.org/ja/latest/method/String/i/to_c.html) でもいける。ただし文字列の形式がおかしいときに例外は出ず、パースできるところまでを数値に変える。

```ruby
def func(str)
	str.to_c.real
end
```

## `Complex` の能力
`Complex` は複素数の数値を扱うためのクラスだが、虚部をゼロにして実数を扱うことに特化しても面白いことができる。

### 数値クラス概要
Rubyで用意されている数値クラスは、 `Numeric` を抽象クラスとしてその下にいくつかのサブクラスがある。

| クラス | 表せる数 | 特徴 |
|:-:|:-:|:-:|:-:|
| `Integer`<br>(`Fixnum`) | 整数の一部<br>[int型](https://ja.wikipedia.org/wiki/整数型)に類似 | -2<sup>62</sup>〜2<sup>62</sup>-1 の整数を表す[^int-range] |
| `Integer`<br>(`Bignum`) | 全ての整数 | `Fixnum` の範囲外の整数をカバーする |
| `Rational` | 全ての有理数 | 整数のペアで表す |
| `Float` | 実数の一部<br>[double型](https://ja.wikipedia.org/wiki/倍精度浮動小数点数)に類似 | 十進15桁程度の精度で<br>10<sup>-308</sup>〜10<sup>308</sup>程度までの幅広い数を扱う |
| `BigDecimal` | 全ての実数 | 精度も扱える大きさも自由<br>（標準ライブラリ） |
| `Complex` | 全ての複素数 | **実数のペアで表す** |

[^int-range]: 一般的なint型より1ビット分だけ少ないのはCRubyの実装によるもの

注釈：

* `Integer` はRuby2.3まで抽象クラスであり、サブクラスとして `Fixnum` と `Bignum` に分かれていた。とはいえ数値の大きさに応じて勝手に切り替えてくれるので、現在と同じくユーザーが違いを意識する必要はあまり無かった。
* `Float` と `BigDecimal` を「実数」扱いしているが、厳密にはメモリが有限のため無理数を正確に表すことができない。「精度」という曖昧さによって近似的に実数を表す。
* 「全ての整数」などと言っているのも、実際にはメモリが有限のため限界がある。

数学的に同じ数であっても、例えば `3`, `(3/1)`, `((3/1)+0i)` というように、異なるクラスで表すことができる[^float-eq]。このような状況では**なるべく単純なクラスで表したほうが効率がいい**。その辺を考慮してくれるライブラリに [**mathn**](https://docs.ruby-lang.org/ja/2.4.0/library/mathn.html) があったが、プログラム全体で演算の動作を変えてしまうため、Ruby2.2で非推奨になりRuby2.5で廃止された。

[^float-eq]: 浮動小数点数 `3.0` は、厳密に `3` ではなく*限りなく近いだけ*かもしれないので、 `3` と同一視していいかは都度考える必要がある

数学においては

```math
自然数 \mathbb{N} \subset 整数 \mathbb{Z} \subset 有理数 \mathbb{Q} \subset 実数 \mathbb{R} \subset 複素数 \mathbb{C}
```

という階層構造があるが、数値クラスはそのような継承（is-a）関係は無いし、包含（has-a）関係があるとも限らない。そのため**実数クラス同士がほぼ独立**していて、うまく纏めて扱うことが難しい。

しかし幸いなことに、複素数クラス `Complex` は**任意の実数クラスを持てる**ので、これをラッパーのように利用することで実数クラスを纏めて扱える。

### 文字列のパース
任意の実数クラスを持てるという性質上、文字列のパースでは自動で適切な実数クラス（ `Integer`, `Rational`, `Float` ）を選んでくれる。

認識される文字列の構文は[マニュアル](http://ruby-doc.org/core-2.6/Kernel.html#method-i-Complex)で確認できる。具体的にどの実数クラスになるかは、ソースを読み解くか実際に試してみるしかない。

>
```text:Syntax&#x20;of&#x20;string&#x20;form&#x3a;
string form = extra spaces , complex , extra spaces ;
complex = real part | [ sign ] , imaginary part
        | real part , sign , imaginary part
        | rational , "@" , rational ;
real part = rational ;
imaginary part = imaginary unit | unsigned rational , imaginary unit ;
rational = [ sign ] , unsigned rational ;
unsigned rational = numerator | numerator , "/" , denominator ;
numerator = integer part | fractional part | integer part , fractional part ;
denominator = digits ;
integer part = digits ;
fractional part = "." , digits , [ ( "e" | "E" ) , [ sign ] , digits ] ;
imaginary unit = "i" | "I" | "j" | "J" ;
sign = "-" | "+" ;
digits = digit , { digit | "_" , digit };
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
extra spaces = ? \s* ? ;
```

### 整数 / 整数
多くの言語では `整数 / 整数` を計算すると小数点以下を切り捨てた整数を返す。Rubyの `Integer` も同じであり、もし切り捨てでない正確な値を返してほしければ、演算子でなく `#quo` を明示的に呼び出す必要がある（結果は必ず `Rational` ）。当たり前な話に聞こえるが、 `a / b` のように**変数の中身が `Integer` かそれ以外か分からないとき**にはバグの原因になる。かといって mathn ライブラリのように `Integer#/` の挙動を（グローバルに）変えてしまうと、別の場所でバグとなる危険がある。

`Complex` でラップしてあれば、演算子が `#quo` を使うようになっているので間違えることは無い。演算子の右側が `Complex` でさえあれば、[クラス変換](https://docs.ruby-lang.org/ja/latest/method/Numeric/i/coerce.html)によって `Complex` 同士の演算に帰着する。

```ruby
# Integer 同士の除算
Integer("10")  /  Integer("3")   #=> 3
Integer("10").quo Integer("3")   #=> (10/3)

# Complex が絡む除算
Complex("10")  /  Complex("3")   #=> ((10/3)+0i)
Complex("10")  /  Integer("3")   #=> ((10/3)+(0/1)*i)
Integer("10")  /  Complex("3")   #=> ((10/3)+0i)
```

### 有理数の正規化
演算の結果が `Rational` で分母が1のときというのは結局のところ整数であるため、 `Integer` に変換したほうがその後の処理を効率化できる。（mathnがしていたことのひとつ）

Ruby2.6.0では特定の条件でその正規化処理をするようになった。前節の除算の例で虚部が `0i` だったり `(0/1)*i` だったりしているが、この違いは**除数が `Complex` かどうか**である。Rubyのソースコードからも確認できる。
https://github.com/ruby/ruby/commit/929e971

あくまで `Complex` で割ったときの動作であり、有理数（ `Integer`, `Rational` ）で割ったり他の演算をしたりしたときには働かない。とはいえ `Complex(1)` で割ることで明示的に正規化できる。

```ruby
Complex("3/5-4/5i") / Complex("1/5")                #=> (3-4i)
Complex("3/5-4/5i") / Rational("1/5")               #=> ((3/1)-(4/1)*i)
Complex("3/5-4/5i") / Rational("1/5") / Complex(1)  #=> (3-4i)
```

## 応用例
[仕事算をプログラムで計算する](/HMMNRST/items/8dca7866b70199a0c212)際に `Complex` を使用した。

```math
R = \frac{1}{\frac{1}{R_1} + \frac{1}{R_2} + \cdots}
```

```shell-session:terminal
$ ruby -v
ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-linux]
$ PG='puts (1 / ARGV.sum { |s| 1 / Complex(s) }).real'

$ ruby -e "$PG" --  2.4 9/3 4
1.0
$ ruby -e "$PG" -- 12/5 9/3 4
1
```

* 引数が `Integer`, `Rational`, `Float` いずれの文字列形式でも動作する
	* `Complex("2.4") #=> (2.4+0i)`
	* `Complex("9/3") #=> ((3/1)+0i)`
	* `Complex("4")   #=> (4+0i)`
* 除数が常に `Complex` なので `#/` を使っても整商になってしまうことは無い
	* `1 / Integer("4") #=> 0`
	* `1 / Complex("4") #=> ((1/4)+0i)`
* 除算の答えの成分が `Rational` で分母が1なら `Integer` に直される（Ruby2.6）
	* `1 / Complex("1/1+0i") #=> (1+0i)`

<!--

## 余談
私が `Complex` の挙動に注目しているのには背景がある。

以前に複素数の拡張である四元数（クォータニオン）を扱うクラス `Quaternion` の[ライブラリを作った](/HMMNRST/items/3c501af2488158103da8)ことがあり、その際には `Complex` の動作をかなり忠実に真似た。

* 文字列をパースする際に実数クラスを適切に選ぶ挙動は、真似するのに苦労した。
	* 残念なことに、当時は冒頭の「面倒な方法」しか思い浮かばなかった。
* Ruby2.6で `Complex` の除算に正規化処理が入ったことで、 `Quaternion` のテストが落ちた。
	* 挙動変更に追従したいところではあるが、正規化処理のポリシーが見えず足踏み中。

-->