{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Domain.Math.Data.DecimalFraction
( DecimalFraction(..), fromDouble, validDivisor, digits
) where
import Control.Monad
import Data.Maybe
import Data.Ratio
import Domain.Math.Safe
import Test.QuickCheck
newtype DecimalFraction = DF Rational
deriving (Eq, Ord, Num, Real, Arbitrary)
instance Show DecimalFraction where
show d@(DF r) = show x ++ "." ++ replicate extra '0' ++ show y
where
digs = digits d
base = 10^digs
n = numerator (r * fromInteger base)
(x, y) = n `divMod` base
extra = digs - length (show y)
instance Fractional DecimalFraction where
a/b = fromMaybe (error "invalid divisor") (safeDiv a b)
fromRational r = fromInteger (numerator r) / fromInteger (denominator r)
instance SafeDiv DecimalFraction where
safeDiv (DF a) (DF b) = do
guard (validDivisor (DF b))
fmap DF (a `safeDiv` b)
instance SafePower DecimalFraction where
safePower x (DF r)
| denominator r /= 1 = Nothing
| y >= 0 = Just a
| otherwise = safeDiv 1 a
where
y = numerator r
a = x Prelude.^ abs y
safeRoot x y = safeRecip y >>= safePower x
fromDouble :: Double -> DecimalFraction
fromDouble d = DF (fromInteger base / 10^digs)
where
digs = 8 :: Int
base = round (d * 10^digs) :: Integer
validDivisor :: DecimalFraction -> Bool
validDivisor (DF a) = validDenominator (abs (numerator a))
digits :: DecimalFraction -> Int
digits (DF r) = head $ filter p [0..]
where
p i = 10^i `mod` denominator r == 0
validDenominator :: Integer -> Bool
validDenominator n
| n == 0 = False
| even n = validDenominator (n `div` 2)
| n `mod` 5 == 0 = validDenominator (n `div` 5)
| otherwise = n == 1