module Numeric.Decimal.Rounding ( Rounding(..) , RoundDown , RoundHalfUp , RoundHalfEven , RoundCeiling , RoundFloor , RoundHalfDown , RoundUp , Round05Up ) where import Prelude hiding (exponent) import Numeric.Decimal.Number import Numeric.Decimal.Precision -- | A rounding algorithm to use when the result of an arithmetic operation -- exceeds the precision of the result type class Rounding r where round :: Precision p => Number p r -> Number p r isRoundFloor :: Number p r -> Bool isRoundFloor _ = False -- Required... -- | Round toward 0 (truncate) data RoundDown instance Rounding RoundDown where round = roundDown -- | If the discarded digits represent greater than or equal to half (0.5) of -- the value of a one in the next left position then the value is rounded -- up. If they represent less than half, the value is rounded down. data RoundHalfUp instance Rounding RoundHalfUp where round = roundHalfUp -- | If the discarded digits represent greater than half (0.5) of the value of -- a one in the next left position then the value is rounded up. If they -- represent less than half, the value is rounded down. If they represent -- exactly half, the value is rounded to make its rightmost digit even. data RoundHalfEven instance Rounding RoundHalfEven where round = roundHalfEven -- | Round toward +∞ data RoundCeiling instance Rounding RoundCeiling where round = roundCeiling -- | Round toward −∞ data RoundFloor instance Rounding RoundFloor where round = roundFloor isRoundFloor _ = True -- Optional... -- | If the discarded digits represent greater than half (0.5) of the value of -- a one in the next left position then the value is rounded up. If they -- represent less than half or exactly half, the value is rounded down. data RoundHalfDown instance Rounding RoundHalfDown where round = roundHalfDown -- | Round away from 0 data RoundUp instance Rounding RoundUp where round = roundUp -- | Round zero or five away from 0 data Round05Up instance Rounding Round05Up where round = round05Up -- Implementations rounded :: (Coefficient -> Coefficient -> Coefficient -> Number p r -> Number p r -> Number p r) -> Int -> Number p r -> Number p r rounded f d n = raiseSignal Rounded rounded' where rounded' | r /= 0 = raiseSignal Inexact n' | otherwise = n' p = 10 ^ d (q, r) = coefficient n `quotRem` p n' = f (p `quot` 2) q r down up down = n { coefficient = q , exponent = exponent n + fromIntegral d } up = n { coefficient = q + 1 , exponent = exponent n + fromIntegral d } roundDown :: Precision p => Number p r -> Number p r roundDown n = roundDown' (excessDigits n) where roundDown' Nothing = n roundDown' (Just d) = rounded choice d n choice _h _q _r down _up = down roundHalfUp :: Precision p => Number p r -> Number p r roundHalfUp n = roundHalfUp' (excessDigits n) where roundHalfUp' Nothing = n roundHalfUp' (Just d) = rounded choice d n choice h _q r down up | r >= h = roundHalfUp up | otherwise = down roundHalfEven :: Precision p => Number p r -> Number p r roundHalfEven n = roundHalfEven' (excessDigits n) where roundHalfEven' Nothing = n roundHalfEven' (Just d) = rounded choice d n choice h q r down up = case r `Prelude.compare` h of LT -> down GT -> roundHalfEven up EQ | even q -> down | otherwise -> roundHalfEven up roundCeiling :: Precision p => Number p r -> Number p r roundCeiling n = roundCeiling' (excessDigits n) where roundCeiling' Nothing = n roundCeiling' (Just d) = rounded choice d n choice _h _q r down up | r == 0 || sign n == Neg = down | otherwise = roundCeiling up roundFloor :: Precision p => Number p r -> Number p r roundFloor n = roundFloor' (excessDigits n) where roundFloor' Nothing = n roundFloor' (Just d) = rounded choice d n choice _h _q r down up | r == 0 || sign n == Pos = down | otherwise = roundFloor up roundHalfDown :: Precision p => Number p r -> Number p r roundHalfDown n = roundHalfDown' (excessDigits n) where roundHalfDown' Nothing = n roundHalfDown' (Just d) = rounded choice d n choice h _q r down up | r > h = roundHalfDown up | otherwise = down roundUp :: Precision p => Number p r -> Number p r roundUp n = roundUp' (excessDigits n) where roundUp' Nothing = n roundUp' (Just d) = rounded choice d n choice _h _q r down up | r == 0 = down | otherwise = roundUp up round05Up :: Precision p => Number p r -> Number p r round05Up n = round05Up' (excessDigits n) where round05Up' Nothing = n round05Up' (Just d) = rounded choice d n choice _h q r down up | r == 0 = down | d == 0 || d == 5 = round05Up up -- overflow -> roundDown? | otherwise = down where d = q `rem` 10