円周率に近い分数とは
$\frac{355}{113}$ はなかなか円周率に近い。
\begin{array}
\hbox{}\pi &=& 3.14159265\ldots \\
\frac{355}{113} &=& 3.14159292\ldots \\
\end{array}
なので,なんと 7 桁も一致している。
覚え方
本題に関係ないが,覚え方を書いておく。
[1] 奇数を順に三つ書く:135
[2] それぞれダブルにする:113355
[3] ど真ん中に「分の」を入れる:113 分の 355
いや,こんなもん覚えて何の役に立つの?
まあ,あれだ,手元にパソコンは無いけど電卓(関数電卓じゃないやつ)はあって,円周率を含む 4 桁以上の精度の計算がしたいときだな。
半世紀生きてきて,そういう機会は無かったけどな。仮にあったとしても円周率は 50 桁くらい覚えてるしな。
任意の精度で
では,もう少し精度を高めたいとき,分子・分母をどうやって求めればいいのだろうか。
あるいは,精度はもっと低くてよいから,もっと簡単な分数で表したいとしたら?
Ruby には Float オブジェクトを,与えられた許容誤差のもとになるべく簡単な分数の Rational オブジェクトに変換する Float#rationalize というメソッドがある。素晴らしい!
また,円周率は Math::PI という定数として定義されている。
この二つの道具立てを使うと,例えば,許容誤差 0.1 の範囲でなら,
p Math::PI.rationalize(0.1) # => (16/5)
のようにして得られる。
$\frac{16}{5}$ は 3.2 だから,円周率より 0.058 くらい大きな値だ。確かに許容誤差に収まっている。
許容誤差 0.00000001 なら
p Math::PI.rationalize(0.00000001) # => (100798/32085)
となる。これだと 8 桁一致する。
ただし,注意しなければならないのは,Math::PI は真の円周率ではない,ということ。
あくまで Float オブジェクトとして表せる円周率の近似値に過ぎない。
だから
Math::PI.rationalize(0.000000000000000001)
みたいなことをしても意味が無い1。
許容誤差を与えないとどうなる
ここからは本記事の主題から外れる。せっかく Float#rationalize
が出てきたので,これについてもう少し見ておこう,ということだ。
このメソッドは引数(許容誤差)を与えない使い方もできる。
さきほど示したリンク先の公式リファレンスの説明はやや不正確だが,Float#rationalize
に引数を与えない場合,その Float が持ちうる精度くらいの許容誤差を与えたのと同じになるらしい。
Float で表せる数は,数直線上に均一に分布してはいない。絶対値が 0 に近いところでは非常に密に分布しているが,絶対値が大きいところではスカスカになっている2。
これは浮動小数点数の仕組みを知っていれば容易に理解できることだ3。
言い換えれば,ある浮動小数点数とその両隣の浮動小数点数との間隔は場所によって違っている,というわけだ。
引数を与えない rationalize
は,self
とその隣との間隔くらいの許容誤差のもとに Rational オブジェクトを作ってくれる。
いや,隣との間隔の半分か? うーん,そうでもないようだな。
正確なところは私にも分からないが,だいたいそのくらいの許容誤差であるようだ。
0.1.rationalize
は厳密に 0.1
浮動小数点数についてある程度理解している人なら,Ruby の浮動小数点数リテラル 0.1
が数学的な 0.1 とはわずかに違う数を表していることを知っていると思う。
Ruby の浮動小数点数はふつう基数が 2 である,言い換えれば有効数字の部分が 2 進数で表される。
数学的な 0.1 は 2 進数では無限小数になってしまい,これを有限桁で切ると誤差が出てしまう,ということだ。
ところが
p 0.1.rationalize # => (1/10)
となる。
Ruby の 0.1
から見た数学的な 0.1 は,0.1
の隣の浮動小数点数よりも近いところにある。だから引数無しの rationalize
で変換すると,1/10 にあたる Rational オブジェクトが得られる,ということのようだ(よく分かんないけど)。
Float#to_r
とは違うのか?
実は Float を Rational に変換するメソッドとしては,rationalize
のほかに Float#to_r というものもある。
これは違うものなのか? 違う。
実は浮動小数点数は(Float::INFINITY
や Float::NAN
といったものを除き)すべて数学的には有理数(整数分の整数で表せる数)である。
Float#to_r
は自身が表している有理数に対応する Rational オブジェクトを返す。
したがって,浮動小数点数とそれを to_r
したものとは,数学的に厳密に等しい。
では Ruby の 0.1
はどんな有理数なのか。
p 0.1.to_r # => => (3602879701896397/36028797018963968)
分子分母の数字列を比べれば分かるように,分子の 10 倍と分母とは非常に近い。前者のほうが 2 だけ大きいので,Ruby の 0.1
は数学的な 0.1 よりもごくわずかに大きい数値であることが分かる4。
おわりに
実はこの記事は円周率の有理数近似について書きたかったのではなく,わりと最近知った Float#to_r
や Float#rationalize
が面白く思えたので,紹介しようと思って書いた。