argo-0.2021.11.13: source/library/Argo/Type/Decimal.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
module Argo.Type.Decimal where
import Data.Ratio ((%))
import qualified Argo.Vendor.DeepSeq as DeepSeq
import qualified Argo.Vendor.TemplateHaskell as TH
import qualified Data.List as List
import qualified Data.Ratio as Ratio
import qualified GHC.Generics as Generics
import qualified Numeric
data Decimal = Decimal Integer Integer
deriving (Eq, Generics.Generic, TH.Lift, DeepSeq.NFData, Show)
negate :: Decimal -> Decimal
negate (Decimal s e) = Decimal (-s) e
decimal :: Integer -> Integer -> Decimal
decimal s = normalize . Decimal s
normalize :: Decimal -> Decimal
normalize (Decimal s e) = if s == 0
then Decimal 0 0
else
let (q, r) = quotRem s 10
in if r == 0 then normalize $ Decimal q (e + 1) else Decimal s e
fromInteger :: Integer -> Decimal
fromInteger = flip decimal 0
toInteger :: Decimal -> Maybe Integer
toInteger (Decimal s e) = if e < 0 then Nothing else Just $ s * 10 ^ e
fromRealFloat :: RealFloat a => a -> Maybe Decimal
fromRealFloat x = if isNaN x || isInfinite x then Nothing else
Just
. (if x < 0 then Argo.Type.Decimal.negate else id)
. uncurry fromDigits
. Numeric.floatToDigits 10
$ abs x
toRealFloat :: RealFloat a => Decimal -> a
toRealFloat = Prelude.fromRational . Argo.Type.Decimal.toRational
fromDigits :: [Int] -> Int -> Decimal
fromDigits ds e = uncurry decimal $ List.foldl'
(\(a, n) d -> (a * 10 + Prelude.toInteger d, n - 1))
(0, Prelude.toInteger e)
ds
toRational :: Decimal -> Rational
toRational d@(Decimal s e) =
maybe (s % (10 ^ (-e))) fromIntegral $ Argo.Type.Decimal.toInteger d
fromRational :: Rational -> Maybe Decimal
fromRational r =
let
n = Ratio.numerator r
d1 = Ratio.denominator r
(t, d2) = factor 2 (0 :: Integer) d1
(f, d3) = factor 5 (0 :: Integer) d2
p = max t f
in if d3 == 1
then Just $ decimal (n * 2 ^ (p - t) * 5 ^ (p - f)) (-p)
else Nothing
-- factor d 0 x = (p, y) <=> x = (d ^ p) * y
factor :: (Num a, Integral b) => b -> a -> b -> (a, b)
factor d n x =
let (q, r) = quotRem x d
in if x /= 0 && r == 0 then factor d (n + 1) q else (n, x)