例えば3.14を分数で近似するには、分子と分母をどう選べばよいでしょうか。調べてみるとなかなか奥が深い問題です。
方法1. floatのas_integer_ratio()
以下のように、as_integer_ratio()を用いることができます。
>>> (0.25).as_integer_ratio() (1, 4)
これは1/4
が0.25を近似する分数であることを意味しています。PythonでもRubyのように数値がメソッドを持つことがあるというのはちょっとした驚きです。
ただ、この方法で0.1
を分数で近似しようとすると、以下のようにとてつもなく大きな分子・分母のペアが返ってしまいます。
>>> (0.1).as_integer_ratio() (3602879701896397, 36028797018963968)
ここで、as_integer_ratio()
の仕様を調べてみると、as_integer_ratio()
は無条件にもっともよい近似を探しているわけではなく、分母が2のN乗という条件のもとで近似を探していることが原因のようです(参考:python - Implementation limitations of float.as_integer_ratio() - Stack Overflow)。
実際、36028797018963968 は2の55乗です。しかし、0.1を近似するのにこの結果だと少々大仰すぎる感じがします。
方法2. Fraction
fraction
モジュールを使っても分子・分母のペアを得られます。
>>> from fractions import Fraction >>> Fraction(0.1) Fraction(3602879701896397, 36028797018963968)
方法2は方法1と比べて、分母の最大値を引数でコントロールできる利点があります。例えば、円周率を1000以下の整数の割り算で近似したければ、以下のようにできます。
>>> import math >>> Fraction(math.pi).limit_denominator(1000) Fraction(355, 113)
355/113 = 3.1415929203539825... なので、なかなかよく近似できています。ただ、これがこの条件下で最良の結果なのかは不明です。また、 この方法でも分母を2のN乗に限定したい場合がありそうですが、その方法は不明です。
方法3. Decimal
Pythonの標準モジュールであるdecimal
を使うと、10進数で表された小数、例えば0.1
や3.14
を正確に表すことができます。小学校の小数点に関するドリルを正確に解くための道具だと思えばよいと思います。
decimal
を使うと、以下のように正確に分数を求められました。とはいっても、自明な分数を求めたあとに分子と分母を最大公約数で割っているだけなので、あまり面白みはありません。
>>> from decimal import Decimal >>> Decimal('3.14').as_integer_ratio() (157, 50)
方法3はPython 3.6以降限定です。
使い道
組み込み用途で役立ちそうです。浮動小数の定数を事前に分数で近似しておくことで、コストが高い浮動小数演算の代わりに整数演算が使えます。
おまけ
python - Implementation limitations of float.as_integer_ratio() - Stack Overflow を見ていて知った情報ですが、 江戸時代の有馬 頼徸は、円周率を29桁まで近似する分数を1766年に求めていたそうです(Arima Yoriyuki - Wikipedia) 。ページから計算結果を以下に引用します。手計算でPython版より高い精度で近似できているのは驚きです。
print "python: ",Decimal(884279719003555) / Decimal(281474976710656) print "Arima: ",Decimal(428224593349304) / Decimal(136308121570117) print "Wiki: 3.14159265358979323846264338327950288" # 実行結果 python: 3.14159265358979311599796346854418516 Arima: 3.14159265358979323846264338327569743 Wiki: 3.14159265358979323846264338327950288
参考文献
- 9.4. decimal — 十進固定及び浮動小数点数の算術演算 — Python 3.6.3 ドキュメント
- 9.5. fractions — 有理数 — Python 3.6.3 ドキュメント
- 15. Floating Point Arithmetic: Issues and Limitations — Python 3.6.3 documentation
- python - How to convert a decimal number into fraction? - Stack Overflow
- python - Implementation limitations of float.as_integer_ratio() - Stack Overflow
- Arima Yoriyuki - Wikipedia