{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ParallelListComp    #-}
{-# OPTIONS_HADDOCK show-extensions #-}
module Data.ExactPi
(
  ExactPi(..),
  approximateValue,
  isZero,
  isExact,
  isExactZero,
  isExactOne,
  areExactlyEqual,
  isExactInteger,
  toExactInteger,
  isExactRational,
  toExactRational,
  rationalApproximations,
  
  getRationalLimit
)
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)     = [0]
rationalApproximations (Exact 0 q)     = [q]
rationalApproximations (Exact z q)
  | even z    = [q * 10005^^k * c^^z     | c <- chudnovsky]
  | otherwise = [q * 10005^^k * c^^z * r | c <- chudnovsky | r <- rootApproximation]
  where k = z `div` 2
chudnovsky :: [Rational]
chudnovsky = [426880 / s | s <- partials]
  where lk = iterate (+545140134) 13591409
        xk = iterate (*(-262537412640768000)) 1
        kk = iterate (+12) 6
        mk = 1: [m * ((k^(3::Int) - 16*k) % (n+1)^(3::Int)) | m <- mk | k <- kk | n <- [0..]]
        values = [m * l / x | m <- mk | l <- lk | x <- xk]
        partials = scanl1 (+) values
getRationalLimit :: Fractional a => (a -> a -> Bool) -> [Rational] -> a
getRationalLimit cmp = go . map fromRational
  where go (x:y:xs)
          | cmp x y   = y
          | otherwise = go (y:xs)
        go [x] = x
        go _ = error "did not converge"
rootApproximation :: [Rational]
rootApproximation = map head . iterate (drop 4) $ go 1 0 100 1 40
  where
    go pk' qk' pk qk a = (pk % qk): go pk qk (pk' + a*pk) (qk' + a*qk) (240-a)
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 = (*)