はじめに
rationalizeメソッドとto_rメソッド、どちらもレシーバーを有理数に変換したものを返してくれる「同じ」メソッドだと勘違いしてました。(sizeメソッドとlengthメソッドみたいな)
そんな私が、rubyコミュニティーの強い人たちに質問して得た知識をまとめます。
to_rメソッドとrationalizeメソッドとは
レシーバーを有理数に変換します。同じ結果が取得できる時もあります
a = 0.25
a.to_r ==>(1/4)
a.rationalize ==>(1/4)
しかし
b = 0.1
b.to_r ==>(3602879701896397/36028797018963968)
b.rationalize ==>(1/10)
というふうに結果が異なる時もあります。
- to_rメソッド
https://docs.ruby-lang.org/ja/latest/method/Float/i/to_r.html - rationalizeメソッド
https://docs.ruby-lang.org/ja/latest/method/Float/i/rationalize.html
両者の決定的な違い
Float#to_r は Float の値と等しい有理数を返します。
一方、Float#rationalize は Float の値を近似する有理数を返します。
では実例を以下で考えていきます。
b = 0.1
b.to_r ==>(3602879701896397/36028797018963968)
b.rationalize ==>(1/10)
そもそも、なぜb.to_r
の場合、(3602879701896397/36028797018963968)という複雑な数字になってしまうのでしょうか。
それには、"0.1"という文字列をFloatオブジェクトに変換する過程で丸め誤差が起きてしまっていることが関係しています。プログラムのテキストは見た目が数値でも文字列扱いなので、"0.1"という文字列をFloatオブジェクトに変換するというのが、プログラム実行開始より前に発生します。
その際、コンピュータは内部的に2進数を使って計算を行なっているのですが、0.1は2進数では(有限桁数で)表現しきれないため丸め誤差が起こるようです。
そして、丸め誤差を含んだFloatオブジェクトをto_rメソッドで有理数に変換しているので(3602879701896397/36028797018963968)という複雑な数字になっています。
ではなぜrationalizeメソッドの場合は(1/10)というスッキリとした数字なのかというと、rationalizeメソッドはFloatの値に近い有理数を探索してしてくれるためです。(3602879701896397/36028797018963968)に近い(1/10)を探し出して返してくれます。
その他の違い
rationalizeはStringクラスのオブジェクトに対して使用できない
String#to_rは存在しますが、String#rationalizeは存在しません。
"0.25".to_r ==>(1/4)
"0.25".rationalize ==>undefined method `rationalize' for "0.25":String
rationalizeメソッドが使えるオブジェクトはIntegerクラス、Floatクラス、NilClassクラスのオブジェクトのみです。
ですので、例えば競プロで入力を受け取る時
arr = gets.chomp.split(" ").map(&:rationalize)
とはできないので注意です。