{-# OPTIONS_GHC -Wno-partial-type-signatures #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : Numeric.MixedType.Power Description : Bottom-up typed exponentiation Copyright : (c) Michal Konecny License : BSD3 Maintainer : mikkonecny@gmail.com Stability : experimental Portability : portable -} module Numeric.MixedTypes.Power ( -- * Exponentiation CanPow(..), CanPowBy , (^), (^^) , powUsingMul, integerPowCN , powUsingMulRecip , CanTestIsIntegerType(..) -- ** Tests , specCanPow ) where import Utils.TH.DeclForTypes import Numeric.MixedTypes.PreludeHiding import qualified Prelude as P import Text.Printf import Test.Hspec import Test.QuickCheck import Numeric.CollectErrors ( CN, cn, unCN ) import qualified Numeric.CollectErrors as CN 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.Mul -- import Numeric.MixedTypes.Div () {---- Exponentiation -----} infixl 8 ^, ^^ (^) :: (CanPow t1 t2) => t1 -> t2 -> PowType t1 t2 (^) = pow (^^) :: (CanPow t1 t2) => t1 -> t2 -> PPowType t1 t2 (^^) = ppow {-| A replacement for Prelude's binary `P.^` and `P.^^`. -} class CanPow b e where type PowType b e type PPowType b e type PPowType b e = PowType b e type PowType b e = b -- default pow :: b -> e -> PowType b e ppow :: b -> e -> PPowType b e default ppow :: (PPowType b e ~ PowType b e) => b -> e -> PPowType b e ppow = pow {-| Ability to detect whether a numeric type is restricted to (a subset of) integers. This is useful eg when checking the arguments of the power operator in the CN instance for power. -} class CanTestIsIntegerType t where isIntegerType :: t -> Bool isIntegerType _ = False instance CanTestIsIntegerType t => CanTestIsIntegerType (CN t) where isIntegerType t = isIntegerType (unCN t) instance CanTestIsIntegerType Int where isIntegerType _ = True instance CanTestIsIntegerType Integer where isIntegerType _ = True instance CanTestIsIntegerType Rational instance CanTestIsIntegerType Double integerPowCN :: (HasOrderCertainly b Integer, HasOrderCertainly e Integer, HasEqCertainly b Integer, HasEqCertainly e Integer) => (b -> e -> r) -> CN b -> CN e -> CN r integerPowCN unsafeIntegerPow b n | n ! (b -> e -> r) -> CN b -> CN e -> CN r powCN unsafePow b e | isIntegerType b && isIntegerType e && e ! (b -> e -> r) -> CN b -> CN e -> CN r ppowCN unsafePow b e | b !==! 0 && e !<=! 0 = CN.noValueNumErrorCertain $ CN.OutOfDomain "illegal pow: 0^e with e <= 0" | b ! t -> (t -> t -> t) -> t -> e -> t powUsingMul one mul' x nPre | n < 0 = error $ "powUsingMul is not defined for negative exponent " ++ show n | n == 0 = one | otherwise = aux n where (.*) = mul' n = integer nPre aux m | m == 1 = x | even m = let s = aux (m `P.div` 2) in s .* s | otherwise = let s = aux ((m-1) `P.div` 2) in x .* s .* s powUsingMulRecip :: (CanBeInteger e) => t -> (t -> t -> t) -> (t -> t) -> t -> e -> t powUsingMulRecip one mul' recip' x e | eI < 0 = recip' $ powUsingMul one mul' x (negate eI) | otherwise = powUsingMul one mul' x eI where eI = integer e type CanPowBy t1 t2 = (CanPow t1 t2, PowType t1 t2 ~ t1) {-| HSpec properties that each implementation of CanPow should satisfy. -} specCanPow :: _ => T t1 -> T t2 -> Spec specCanPow (T typeName1 :: T t1) (T typeName2 :: T t2) = describe (printf "CanPow %s %s" typeName1 typeName2) $ do it "x^0 = 1" $ do property $ \ (x :: t1) -> let one = (convertExactly 1 :: t1) in let z = (convertExactly 0 :: t2) in (x ^ z) ?==?$ one it "x^1 = x" $ do property $ \ (x :: t1) -> let one = (convertExactly 1 :: t2) in (x ^ one) ?==?$ x it "x^(y+1) = x*x^y" $ do property $ \ (x :: t1) (y :: t2) -> (isCertainlyNonNegative y) ==> x * (x ^ y) ?==?$ (x ^ (y + 1)) where infix 4 ?==?$ (?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property (?==?$) = printArgsIfFails2 "?==?" (?==?) instance CanPow Integer Integer where type PowType Integer Integer = Integer type PPowType Integer Integer = Rational pow b = (P.^) b ppow b = (P.^^) (rational b) instance CanPow Integer Int where type PowType Integer Int = Integer type PPowType Integer Int = Rational pow b = (P.^) b ppow b = (P.^^) (rational b) instance CanPow Int Integer where type PowType Int Integer = Integer type PPowType Int Integer = Rational pow b = (P.^) (integer b) ppow b = (P.^^) (rational b) instance CanPow Int Int where type PowType Int Int = Rational pow b = (P.^^) (rational b) instance CanPow Rational Int where pow = (P.^^) instance CanPow Rational Integer where pow = (P.^^) instance CanPow Double Int where pow = (P.^^) instance CanPow Double Integer where pow = (P.^^) instance CanPow Double Double where type PowType Double Double = Double pow = (P.**) instance CanPow Double Rational where type PowType Double Rational = Double pow b e = b ^ (double e) instance CanPow Rational Double where type PowType Rational Double = Double pow b e = (double b) ^ e instance CanPow Integer Double where type PowType Integer Double = Double pow b e = (double b) ^ e instance CanPow Int Double where type PowType Int Double = Double pow b e = (double b) ^ e -- instance (CanPow a b) => CanPow [a] [b] where -- type PowType [a] [b] = [PowType a b] -- pow (x:xs) (y:ys) = (pow x y) : (pow xs ys) -- pow _ _ = [] instance (CanPow a b) => CanPow (Maybe a) (Maybe b) where type PowType (Maybe a) (Maybe b) = Maybe (PowType a b) pow (Just x) (Just y) = Just (pow x y) pow _ _ = Nothing instance (CanPow b e, HasOrderCertainly b Integer, HasOrderCertainly e Integer, HasEqCertainly b Integer, CanTestIsIntegerType b, CanTestIsIntegerType e, CanTestInteger e) => CanPow (CN b) (CN e) where type PowType (CN b) (CN e) = CN (PowType b e) type PPowType (CN b) (CN e) = CN (PPowType b e) pow = powCN pow ppow = ppowCN ppow $(declForTypes [[t| Integer |], [t| Int |], [t| Rational |], [t| Double |]] (\ t -> [d| instance (CanPow $t e, HasOrderCertainly e Integer, CanTestIsIntegerType e, CanTestInteger e) => CanPow $t (CN e) where type PowType $t (CN e) = CN (PowType $t e) pow b e = powCN pow (cn b) e type PPowType $t (CN e) = CN (PPowType $t e) ppow b e = ppowCN ppow (cn b) e instance (CanPow b $t, HasOrderCertainly b Integer, HasEqCertainly b Integer, CanTestIsIntegerType b) => CanPow (CN b) $t where type PowType (CN b) $t = CN (PowType b $t) pow b e = powCN pow b (cn e) type PPowType (CN b) $t = CN (PPowType b $t) ppow b e = ppowCN ppow b (cn e) |]))