module Data.Lognum (Lognum)
    where

import Data.Ratio

data Lognum t = L !Int !t deriving (Eq, Ord)

fromFloating :: (Floating t, Ord t) => t -> Lognum t
fromFloating a = case a `compare` 0 of
                LT -> L (-1) (log $ -a)
                EQ -> L 0 0
                GT -> L 1 (log a)

toFloating :: (Floating t, Ord t) => Lognum t -> t
toFloating (L s m) = case s of
                        -1 -> negate $ exp m
                        0  -> 0
                        1  -> exp m

logify2 :: (Floating t, Ord t) => (t -> t -> t) -> (Lognum t -> Lognum t -> Lognum t)
logify2 (*) a b = fromFloating $ toFloating a * toFloating b

instance (Floating t, Ord t) => Show (Lognum t) where
    show = show . toFloating

instance (Floating t, Ord t) => Num (Lognum t) where
    (+) = logify2 (+)

    (L s m) * (L s' m') =   if s == 0 || s' == 0 then
                                L 0 0
                            else
                                L (s*s') (m+m')

    (-) = logify2 (-)

    negate (L s m) = L (negate s) m

    abs (L s m) = L (abs s) m

    signum (L s m) = (L s 0)

    fromInteger = fromFloating . fromInteger

instance (Floating t, Ord t) => Fractional (Lognum t) where
    _ / (L 0 _) = error "division by zero"
    (L s m) / (L s' m') = L (s*s') (m-m')
    
    recip (L s m) = L s (-m)
    
    fromRational x = (fromInteger $ numerator x) / (fromInteger $ denominator x)