LoginSignup
2
0

More than 5 years have passed since last update.

すごい乱暴な方法で Haskell の小数を正確数にする

Posted at

そもそも 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 そんな…

fromRationaltoRational を連続して適用する場合は割と望ましい結果が得られるが,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) (ピコ) の精度は保証できている

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0