{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_HADDOCK show-extensions #-}
module Data.ExactPi
(
ExactPi(..),
approximateValue,
isZero,
isExact,
isExactZero,
isExactOne,
areExactlyEqual,
isExactInteger,
toExactInteger,
isExactRational,
toExactRational,
rationalApproximations
)
where
import Data.Monoid
import Data.Ratio ((%), numerator, denominator)
import Data.Semigroup
import Prelude
data ExactPi = Exact Integer Rational
| Approximate (forall a.Floating a => a)
approximateValue :: Floating a => ExactPi -> a
approximateValue (Exact z q) = (pi ^^ z) * (fromRational q)
approximateValue (Approximate x) = x
isZero :: ExactPi -> Bool
isZero (Exact _ 0) = True
isZero (Approximate x) = x == (0 :: Double)
isZero _ = False
isExact :: ExactPi -> Bool
isExact (Exact _ _) = True
isExact _ = False
isExactZero :: ExactPi -> Bool
isExactZero (Exact _ 0) = True
isExactZero _ = False
isExactOne :: ExactPi -> Bool
isExactOne (Exact 0 1) = True
isExactOne _ = False
areExactlyEqual :: ExactPi -> ExactPi -> Bool
areExactlyEqual (Exact z1 q1) (Exact z2 q2) = (z1 == z2 && q1 == q2) || (q1 == 0 && q2 == 0)
areExactlyEqual _ _ = False
isExactInteger :: ExactPi -> Bool
isExactInteger (Exact 0 q) | denominator q == 1 = True
isExactInteger _ = False
toExactInteger :: ExactPi -> Maybe Integer
toExactInteger (Exact 0 q) | denominator q == 1 = Just $ numerator q
toExactInteger _ = Nothing
isExactRational :: ExactPi -> Bool
isExactRational (Exact 0 _) = True
isExactRational _ = False
toExactRational :: ExactPi -> Maybe Rational
toExactRational (Exact 0 q) = Just q
toExactRational _ = Nothing
rationalApproximations :: ExactPi -> [Rational]
rationalApproximations (Approximate x) = [toRational (x :: Double)]
rationalApproximations (Exact 0 q) = [q]
rationalApproximations (Exact z q) = fmap (\pi' -> q * (pi' ^^ z)) piConvergents
where
piConvergents :: [Rational]
piConvergents = go True 2 4 where
go s p' q' | ltPi m = [q' | not s] ++ go True m q'
| otherwise = [p' | s] ++ go False p' m where
m = (numerator p' + numerator q')%(denominator p' + denominator q')
ltPi :: Rational -> Bool
ltPi x = ok x 1 where
ok y i =
y <= (27*i - 12)%5 ||
(y < (675*i - 216)%125 &&
ok ((y - fromInteger (5*i - 2))*(3*(3*i + 1)*(3*i + 2)%(i*(2*i - 1))))
(i + 1))
instance Show ExactPi where
show (Exact z q) | z == 0 = "Exactly " ++ show q
| z == 1 = "Exactly pi * " ++ show q
| otherwise = "Exactly pi^" ++ show z ++ " * " ++ show q
show (Approximate x) = "Approximately " ++ show (x :: Double)
instance Num ExactPi where
fromInteger n = Exact 0 (fromInteger n)
(Exact z1 q1) * (Exact z2 q2) = Exact (z1 + z2) (q1 * q2)
(Exact _ 0) * _ = 0
_ * (Exact _ 0) = 0
x * y = Approximate $ approximateValue x * approximateValue y
(Exact z1 q1) + (Exact z2 q2) | z1 == z2 = Exact z1 (q1 + q2)
x + y = Approximate $ approximateValue x + approximateValue y
abs (Exact z q) = Exact z (abs q)
abs (Approximate x) = Approximate $ abs x
signum (Exact _ q) = Exact 0 (signum q)
signum (Approximate x) = Approximate $ signum x
negate x = (Exact 0 (-1)) * x
instance Fractional ExactPi where
fromRational = Exact 0
recip (Exact z q) = Exact (negate z) (recip q)
recip (Approximate x) = Approximate (recip x)
instance Floating ExactPi where
pi = Exact 1 1
exp x | isExactZero x = 1
| otherwise = approx1 exp x
log (Exact 0 1) = 0
log x = approx1 log x
sin = approx1 sin
cos = approx1 cos
tan = approx1 tan
asin = approx1 asin
atan = approx1 atan
acos = approx1 acos
sinh = approx1 sinh
cosh = approx1 cosh
tanh = approx1 tanh
asinh = approx1 asinh
acosh = approx1 acosh
atanh = approx1 atanh
approx1 :: (forall a.Floating a => a -> a) -> ExactPi -> ExactPi
approx1 f x = Approximate (f (approximateValue x))
instance Semigroup ExactPi where
(<>) = mappend
instance Monoid ExactPi where
mempty = 1
mappend = (*)