{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Numeric.Decimal
(
module Numeric.Decimal.BoundedArithmetic
, module Numeric.Decimal.Internal
, RoundHalfUp
, roundHalfUp
, RoundHalfDown
, roundHalfDown
, RoundHalfEven
, roundHalfEven
, RoundDown
, Floor
, roundDown
, RoundToZero
, Truncate
, roundToZero
, decimalList
, sumDecimalBounded
, productDecimalBoundedWithRounding
, FixedScale
, toFixedDecimal
, fromFixedDecimal
, fromFixedDecimalBounded
, toScientificDecimal
, fromScientificDecimal
, fromScientificDecimalBounded
) where
import Control.Exception
import Control.Monad
import Control.Monad.Catch
import Data.Coerce
import Data.Fixed
import Data.Int
import Data.Word
import Data.Proxy
import Data.Scientific
import GHC.TypeLits
import Numeric.Decimal.BoundedArithmetic
import Numeric.Decimal.Internal
data RoundHalfUp
instance Round RoundHalfUp Integer where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Int where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Int8 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Int16 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Int32 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Int64 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Word where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Word8 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Word16 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Word32 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfUp Word64 where
roundDecimal = roundHalfUp
{-# INLINABLE roundDecimal #-}
roundHalfUp :: forall r n k p . (Integral p, KnownNat k) => Decimal r (n + k) p -> Decimal r n p
roundHalfUp (Decimal x)
| k == 0 = Decimal x
| r >= s1 = Decimal (q + 1)
| signum r < 0 && abs r > s1 = Decimal (q - 1)
| otherwise = Decimal q
where
k = fromIntegral (natVal (Proxy :: Proxy k)) :: Int
s1 = 10 ^ k
(q, r) = (2 *) <$> quotRem x s1
{-# INLINABLE roundHalfUp #-}
data RoundHalfDown
instance Round RoundHalfDown Integer where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Int where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Int8 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Int16 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Int32 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Int64 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Word where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Word8 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Word16 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Word32 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfDown Word64 where
roundDecimal = roundHalfDown
{-# INLINABLE roundDecimal #-}
roundHalfDown :: forall r n k p . (Integral p, KnownNat k) => Decimal r (n + k) p -> Decimal r n p
roundHalfDown (Decimal x)
| k == 0 = Decimal x
| r > s1 = Decimal (q + 1)
| signum r < 0 && abs r >= s1 = Decimal (q - 1)
| otherwise = Decimal q
where
k = fromIntegral (natVal (Proxy :: Proxy k)) :: Int
s1 = 10 ^ k
(q, r) = (2 *) <$> quotRem x s1
{-# INLINABLE roundHalfDown #-}
data RoundHalfEven
instance Round RoundHalfEven Integer where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Int where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Int8 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Int16 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Int32 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Int64 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Word where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Word8 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Word16 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Word32 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
instance Round RoundHalfEven Word64 where
roundDecimal = roundHalfEven
{-# INLINABLE roundDecimal #-}
roundHalfEven :: forall r n k p . (Integral p, KnownNat k) => Decimal r (n + k) p -> Decimal r n p
roundHalfEven (Decimal x)
| k == 0 = Decimal x
| abs r == s1 && odd q = Decimal (q + signum r)
| abs r == s1 = Decimal q
| r > s1 = Decimal (q + 1)
| signum r < 0 && abs r > s1 = Decimal (q - 1)
| otherwise = Decimal q
where
k = fromIntegral (natVal (Proxy :: Proxy k)) :: Int
s1 = 10 ^ k
(q, r) = (2 *) <$> quotRem x s1
{-# INLINABLE roundHalfEven #-}
data RoundDown
type Floor = RoundDown
instance Round RoundDown Integer where
roundDecimal = roundDown
instance Round RoundDown Int where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Int8 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Int16 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Int32 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Int64 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Word where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Word8 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Word16 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Word32 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
instance Round RoundDown Word64 where
roundDecimal = roundDown
{-# INLINABLE roundDecimal #-}
roundDown :: forall r n k p . (Integral p, KnownNat k) => Decimal r (n + k) p -> Decimal r n p
roundDown (Decimal x)
| x >= 0 || r == 0 = Decimal q
| otherwise = Decimal (q - 1)
where
k = fromIntegral (natVal (Proxy :: Proxy k)) :: Int
(q, r) = quotRem x (10 ^ k)
{-# INLINABLE roundDown #-}
data RoundToZero
type Truncate = RoundToZero
instance Round RoundToZero Integer where
roundDecimal = roundToZero
instance Round RoundToZero Int where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Int8 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Int16 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Int32 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Int64 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Word where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Word8 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Word16 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Word32 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
instance Round RoundToZero Word64 where
roundDecimal = roundToZero
{-# INLINABLE roundDecimal #-}
roundToZero :: forall r n k p . (Integral p, KnownNat k) => Decimal r (n + k) p -> Decimal r n p
roundToZero (Decimal x) = Decimal (quot x (10 ^ k))
where
k = fromIntegral (natVal (Proxy :: Proxy k)) :: Int
{-# INLINABLE roundToZero #-}
decimalList :: Integral p => [p] -> [Decimal r s p]
decimalList = coerce
sumDecimalBounded ::
(MonadThrow m, Foldable f, Eq p, Ord p, Num p, Bounded p)
=> f (Decimal r s p)
-> m (Decimal r s p)
sumDecimalBounded = foldM plusDecimalBounded (Decimal 0)
{-# INLINABLE sumDecimalBounded #-}
productDecimalBoundedWithRounding ::
(MonadThrow m, Foldable f, KnownNat s, Round r Integer, Integral p, Bounded p)
=> f (Decimal r s p)
-> m (Decimal r s p)
productDecimalBoundedWithRounding ds =
fromIntegralDecimalBounded 1 >>=
(\acc -> foldM timesDecimalBoundedWithRounding acc ds)
{-# INLINABLE productDecimalBoundedWithRounding #-}
toScientificDecimal :: (Integral p, KnownNat s) => Decimal r s p -> Scientific
toScientificDecimal dec =
scientific
(toInteger (unwrapDecimal dec))
(fromInteger (negate (getScale dec)))
fromScientificDecimal ::
forall m r s. (MonadThrow m, KnownNat s)
=> Scientific
-> m (Decimal r s Integer)
fromScientificDecimal num
| point10 > s = throwM Underflow
| otherwise = pure (Decimal (coefficient num * 10 ^ (s - point10)))
where
s = natVal (Proxy :: Proxy s)
point10 = toInteger (negate (base10Exponent num))
fromScientificDecimalBounded ::
forall m r s p. (MonadThrow m, Integral p, Bounded p, KnownNat s)
=> Scientific
-> m (Decimal r s p)
fromScientificDecimalBounded num = do
Decimal integer :: Decimal r s Integer <- fromScientificDecimal num
Decimal <$> fromIntegerBounded integer
type family FixedScale e :: Nat
type instance FixedScale E0 = 0
type instance FixedScale E1 = 1
type instance FixedScale E2 = 2
type instance FixedScale E3 = 3
type instance FixedScale E6 = 6
type instance FixedScale E9 = 9
type instance FixedScale E12 = 12
toFixedDecimal :: (s ~ FixedScale e, Integral p) => Decimal r s p -> Fixed e
toFixedDecimal = MkFixed . toInteger . unwrapDecimal
fromFixedDecimal :: s ~ FixedScale e => Fixed e -> Decimal r s Integer
fromFixedDecimal = coerce
fromFixedDecimalBounded ::
(s ~ FixedScale e, MonadThrow m, Integral p, Bounded p)
=> Fixed e
-> m (Decimal r s p)
fromFixedDecimalBounded = fromIntegerDecimalBounded . fromFixedDecimal