packages feed

argo-0.2021.11.11: 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.Ratio as Ratio
import qualified GHC.Generics as Generics

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

toRational :: Decimal -> Rational
toRational (Decimal s e) =
    if e < 0 then s % (10 ^ (-e)) else fromInteger $ s * 10 ^ e

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)