{-| Module : Numeric.MixedType.Round Description : Bottom-up typed round, floor, etc. Copyright : (c) Michal Konecny License : BSD3 Maintainer : mikkonecny@gmail.com Stability : experimental Portability : portable -} module Numeric.MixedTypes.Round ( -- * Rounded division + modulus CanDivIMod(..), CanDivIModIntegerSameType, modNoCN, divINoCN, divIModNoCN -- * Rounding , CanRound(..), HasIntegerBounds(..) -- ** Tests , specCanDivIMod, specCanRound, specHasIntegerBounds ) where import Numeric.MixedTypes.PreludeHiding import qualified Prelude as P import Text.Printf import Data.Fixed (divMod') -- import qualified Data.List as List import Test.Hspec import Test.QuickCheck as QC import Numeric.CollectErrors -- import Control.CollectErrors import Numeric.MixedTypes.Literals import Numeric.MixedTypes.Bool import Numeric.MixedTypes.Eq import Numeric.MixedTypes.Ord -- import Numeric.MixedTypes.MinMaxAbs import Numeric.MixedTypes.AddSub import Numeric.MixedTypes.Ring {---- rounded division + modulo -----} class CanDivIMod t1 t2 where type DivIType t1 t2 type DivIType t1 t2 = CN Integer type ModType t1 t2 type ModType t1 t2 = EnsureCN t1 divIMod :: t1 -> t2 -> (DivIType t1 t2, ModType t1 t2) mod :: t1 -> t2 -> ModType t1 t2 mod a b = snd $ divIMod a b divI :: t1 -> t2 -> DivIType t1 t2 divI a b = fst $ divIMod a b type CanDivIModIntegerSameType t = (CanDivIMod t t, CanEnsureCN t, DivIType t t ~ CN Integer, ModType t t ~ EnsureCN t) modNoCN :: (CanDivIMod t1 t2 , ModType t1 t2 ~ EnsureCN t1, CanEnsureCN t1) => t1 -> t2 -> t1 modNoCN x m = case deEnsureCN $ x `mod` m of Left err -> error $ show err Right xm -> xm divINoCN :: (CanDivIMod t1 t2, DivIType t1 t2 ~ CN Integer) => t1 -> t2 -> Integer divINoCN x m = (~!) $ x `divI` m divIModNoCN :: (CanDivIMod t1 t2 , ModType t1 t2 ~ EnsureCN t1, CanEnsureCN t1 , DivIType t1 t2 ~ CN Integer) => t1 -> t2 -> (Integer, t1) divIModNoCN x m = case deEnsureCN xm of Left err -> error $ show err Right xm2 -> ((~!) d, xm2) where (d,xm) = divIMod x m instance CanDivIMod Integer Integer where divIMod x m | m > 0 = (cn d, cn xm) | otherwise = (err, err) where (d,xm) = P.divMod x m err = noValueNumErrorCertainECN sample_v $ OutOfRange $ "modulus not positive: " ++ show m sample_v = Just x instance CanDivIMod Rational Rational where divIMod x m | m > 0 = (cn d, cn xm) | otherwise = (err (d :: Integer), err xm) where (d,xm) = divMod' x m err :: (CanEnsureCN t) => t -> EnsureCN t err s = noValueNumErrorCertainECN (Just s) $ OutOfRange $ "modulus not positive: " ++ show m instance CanDivIMod Rational Integer where divIMod x m = divIMod x (rational m) instance CanDivIMod Double Double where divIMod x m | m > 0 = (cn d, cn xm) | otherwise = (err (d :: Integer), err xm) where (d,xm) = divMod' x m err :: (CanEnsureCN t) => t -> EnsureCN t err s = noValueNumErrorCertainECN (Just s) $ OutOfRange $ "modulus not positive: " ++ show m instance CanDivIMod Double Integer where divIMod x m = divIMod x (double m) type CanDivIModX t = (CanDivIMod t t, ModType t t ~ EnsureCN t, DivIType t t ~ CN Integer, EnsureNoCN t ~ t, CanEnsureCN t, CanMulBy t Integer, CanAddSameType t, HasOrderCertainly t Integer, HasOrderCertainly t t, HasEqCertainly t t, CanTestFinite t, Show t, Arbitrary t) {-| HSpec properties that each implementation of CanRound should satisfy. -} specCanDivIMod :: (CanDivIModX t, HasIntegers t) => T t -> Spec specCanDivIMod (T typeName :: T t) = describe (printf "CanDivMod %s %s" typeName typeName) $ do it "holds 0 <= x `mod` m < m" $ do property $ \ (x :: t) (m :: t) -> isFinite x && m !>! 0 ==> let xm = x `modNoCN` m in (0 ?<=?$ xm) .&&. (xm ? isFinite x && m !>! 0 ==> let (d,xm) = divIModNoCN x m in (x ?==?$ (d*m + xm)) where (?<=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (?<=?$) = printArgsIfFails2 "?<=?" (?<=?) (? a -> b -> Property (? a -> b -> Property (?==?$) = printArgsIfFails2 "?==?" (?==?) {---- rounding -----} {-| A replacement for Prelude's `P.RealFrac` operations, such as round in which the result type is fixed to Integer. If @RealFrac t@ and @CanTestPosNeg t@, then one can use the default implementation to mirror Prelude's @round@, etc. In other cases, it is sufficient to define `properFraction`. -} class CanRound t where properFraction :: t -> (Integer, t) default properFraction :: (P.RealFrac t) => t -> (Integer, t) properFraction = P.properFraction truncate :: t -> Integer truncate = fst . properFraction round :: t -> Integer default round :: (HasOrderCertainly t Rational) => t -> Integer round x | -0.5 !! 0.5 = n + 1 | even n = n | r !! 0.0 = n + 1 | otherwise = error "round default defn: Bad value" where (n,r) = properFraction x ceiling :: t -> Integer default ceiling :: (CanTestPosNeg t) => t -> Integer ceiling x | isCertainlyPositive r = n + 1 | otherwise = n where (n,r) = properFraction x floor :: t -> Integer default floor :: (CanTestPosNeg t) => t -> Integer floor x | isCertainlyNegative r = n - 1 | otherwise = n where (n,r) = properFraction x instance CanRound Rational instance CanRound Double where round = P.round ceiling = P.ceiling floor = P.floor type CanRoundX t = (CanRound t, CanNegSameType t, CanTestPosNeg t, HasOrderCertainly t Integer, CanTestFinite t, Show t, Arbitrary t) {-| HSpec properties that each implementation of CanRound should satisfy. -} specCanRound :: (CanRoundX t, HasIntegers t) => T t -> Spec specCanRound (T typeName :: T t) = describe (printf "CanRound %s" typeName) $ do it "holds floor x <= x <= ceiling x" $ do property $ \ (x :: t) -> isFinite x ==> (floor x ?<=?$ x) .&&. (x ?<=?$ ceiling x) it "holds floor x <= round x <= ceiling x" $ do property $ \ (x :: t) -> isFinite x ==> (floor x !<=!$ round x) .&&. (round x !<=!$ ceiling x) it "0 <= ceiling x - floor x <= 1" $ do property $ \ (x :: t) -> isFinite x ==> (ceiling x - floor x) `elem_PF` [0,1] it "holds floor x = round x = ceiling x for integers" $ do property $ \ (xi :: Integer) -> let x = convertExactly xi :: t in (floor x !==!$ round x) .&&. (round x !==!$ ceiling x) where (?<=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (?<=?$) = printArgsIfFails2 "?<=?" (?<=?) (!<=!$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (!<=!$) = printArgsIfFails2 "!<=!" (!<=!) (!==!$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (!==!$) = printArgsIfFails2 "!==!" (!==!) elem_PF = printArgsIfFails2 "elem" elem class HasIntegerBounds t where integerBounds :: t -> (Integer, Integer) default integerBounds :: (CanRound t) => t -> (Integer, Integer) integerBounds x = (floor x, ceiling x) instance HasIntegerBounds Rational instance HasIntegerBounds Double instance HasIntegerBounds Integer where integerBounds n = (n,n) instance HasIntegerBounds Int where integerBounds n = (n',n') where n' = integer n type HasIntegerBoundsX t = (HasIntegerBounds t, -- CanNegSameType t, -- CanTestPosNeg t, HasOrderCertainly t Integer, CanTestFinite t, Show t, Arbitrary t) {-| HSpec properties that each implementation of CanRound should satisfy. -} specHasIntegerBounds :: (HasIntegerBoundsX t) => T t -> Spec specHasIntegerBounds (T typeName :: T t) = describe (printf "HasIntegerBounds %s" typeName) $ do it "holds l <= x <= r" $ do property $ \ (x :: t) -> isFinite x ==> let (l,r) = integerBounds x in (l ?<=?$ x) .&&. (x ?<=?$ r) where (?<=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (?<=?$) = printArgsIfFails2 "?<=?" (?<=?)