そもそも Haskell の Prelude で toRational :: Real a => a -> Rational
が宣言されているので,これを使えば小数を有理数として扱えはするけれど,以下のように望ましくない動きをする
import Data.Ratio ((%))
11 % 10 -- Haskell での 11/10 (= 1.1) の有理数表現
fromRational $ 11 % 10 -- 1.1 これは期待通り
fromRational $ toRational 1.1 -- 1.1 これも期待通り
(toRational 1.1) == (toRational 1.1) -- True これも期待通り
toRational 1.1 -- 2476979795053773 % 2251799813685248 えっ
(toRational 1.1) == (11 % 10) -- False そんな…
fromRational
と toRational
を連続して適用する場合は割と望ましい結果が得られるが,toRational
のみを利用して,他の有理数との算術演算をしたりするとどうもおかしい事になりがち.
なので多少乱暴な方法で正確数を Real a
( 実数型クラスのインスタンス ) から得る関数を書く
import Numeric ( floatToDigit, fromRat )
import Data.Ratio ( (%), denominator )
-- | exact ratio number from Real `a`
-- example
-- > exact 1.1 -- 11 % 10
-- > (exact 1.1) == (11 % 10) -- True
-- > fromRational $ exact 1.1 -- 1.1
exactFromReal :: Real a => a -> Rational
exactFromReal x
| asInt = ratio
| otherwise = numer % denom
where
asInt = denominator ratio == 1
ratio = toRational x
numer = round $ ratio * (denom % 1)
denom = 10 ^ (uN - iN)
iN = snd digits
uN = length $ fst digits
digits = floatToDigits 10 $ fromRat ratio
ただし,ここで利用している Numeric.floatToDigits
が,十進で17桁以上は切ってしまうので, 精度はそこまで良いわけではない.
QuickCheck でテストした場合.だいたい ±10^(-12) (ピコ) の精度は保証できている