quadratic-irrational 0.0.1 → 0.0.2
raw patch · 9 files changed
+481/−70 lines, 9 filesdep +directorydep +doctestdep +filepathdep ~basedep ~mtl
Dependencies added: directory, doctest, filepath
Dependency ranges changed: base, mtl
Files
- ChangeLog.md +10/−0
- quadratic-irrational.cabal +17/−3
- src/Numeric/QuadraticIrrational.hs +312/−40
- src/Numeric/QuadraticIrrational/CyclicList.hs +10/−0
- src/Numeric/QuadraticIrrational/Internal/Lens.hs +42/−0
- tests/Main.hs +0/−16
- tests/QuadraticIrrational.hs +42/−11
- tests/doctests.hs +32/−0
- tests/tasty.hs +16/−0
+ ChangeLog.md view
@@ -0,0 +1,10 @@+# 0.0.2 (2014-03-25)++* Add doctests.+* Fix qiModify potentially constructing `qi 1 0 5 1` instead of the equivalent+ but simpler `qi 1 0 0 1`.+* Add lenses.++# 0.0.1 (2014-03-24)++* Initial release.
quadratic-irrational.cabal view
@@ -1,6 +1,6 @@ name: quadratic-irrational category: Math, Algorithms, Data-version: 0.0.1+version: 0.0.2 license: MIT license-file: LICENSE author: Johan Kiviniemi <devel@johan.kiviniemi.name>@@ -21,6 +21,7 @@ cabal-version: >= 1.10 extra-source-files: .gitignore+ ChangeLog.md README.md source-repository head@@ -30,6 +31,7 @@ library exposed-modules: Numeric.QuadraticIrrational , Numeric.QuadraticIrrational.CyclicList+ , Numeric.QuadraticIrrational.Internal.Lens hs-source-dirs: src build-depends: base >= 4.6 && < 4.8 , arithmoi == 0.4.*@@ -39,9 +41,9 @@ default-language: Haskell2010 ghc-options: -Wall -O2 -funbox-strict-fields -test-suite test-quadratic-irrational+test-suite tasty-tests type: exitcode-stdio-1.0- main-is: Main.hs+ main-is: tasty.hs other-modules: QuadraticIrrational , CyclicList hs-source-dirs: tests@@ -53,3 +55,15 @@ , tasty-quickcheck == 0.8.* default-language: Haskell2010 ghc-options: -Wall -O2 -funbox-strict-fields++test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ hs-source-dirs: tests+ build-depends: base+ , directory+ , doctest >= 0.9+ , filepath+ , mtl+ default-language: Haskell2010+ ghc-options: -threaded -Wall
src/Numeric/QuadraticIrrational.hs view
@@ -15,13 +15,19 @@ -- <http://en.wikipedia.org/wiki/Periodic_continued_fraction periodic continued fractions>. module Numeric.QuadraticIrrational- ( QI, qi, qi', qiModify, runQI, runQI', unQI, unQI'- , qiZero, qiOne, qiIsZero+ ( -- * Constructors and deconstructors+ QI, qi, qi', runQI, runQI', unQI, unQI'+ , -- * Lenses+ _qi, _qi', _qiABD, _qiA, _qiB, _qiC, _qiD+ , -- * Numerical operations+ qiZero, qiOne, qiIsZero , qiToFloat , qiAddI, qiSubI, qiMulI, qiDivI , qiAddR, qiSubR, qiMulR, qiDivR , qiNegate, qiRecip, qiAdd, qiSub, qiMul, qiDiv, qiPow- , qiFloor, continuedFractionToQI, qiToContinuedFraction+ , qiFloor+ , -- * Continued fractions+ continuedFractionToQI, qiToContinuedFraction , module Numeric.QuadraticIrrational.CyclicList ) where @@ -36,6 +42,7 @@ import Text.Read import Numeric.QuadraticIrrational.CyclicList+import Numeric.QuadraticIrrational.Internal.Lens -- | @(a + b √c) / d@ data QI = QI !Integer@@ -65,6 +72,32 @@ -- | Given @a@, @b@, @c@ and @d@ such that @n = (a + b √c)/d@, constuct a 'QI' -- corresponding to @n@.+--+-- >>> qi 3 4 5 6+-- qi 3 4 5 6+--+-- The fractions are reduced:+--+-- >>> qi 30 40 5 60+-- qi 3 4 5 6+--+-- If @b = 0@ then @c@ is zeroed and vice versa:+--+-- >>> qi 3 0 42 1+-- qi 3 0 0 1+--+-- >>> qi 3 42 0 1+-- qi 3 0 0 1+--+-- The @b √c@ term is simplified:+--+-- >>> qi 0 1 (5*5*6) 1+-- qi 0 5 6 1+--+-- If @c = 1@ (after simplification) then @b@ is moved to @a@:+--+-- >>> qi 1 5 (2*2) 1+-- qi 11 0 0 1 qi :: Integer -- ^ a -> Integer -- ^ b -> Integer -- ^ c@@ -77,6 +110,16 @@ | otherwise = simplifyReduceCons a b c d {-# INLINE qi #-} +-- Construct a 'QI' without simplifying @b √c@. Make sure it has already been+-- simplified.+qiNoSimpl :: Integer -> Integer -> Integer -> Integer -> QI+qiNoSimpl a b (nonNegative "qiNoSimpl" -> c) (nonZero "qiNoSimpl" -> d)+ | b == 0 = reduceCons a 0 0 d+ | c == 0 = reduceCons a 0 0 d+ | c == 1 = reduceCons (a + b) 0 0 d+ | otherwise = reduceCons a b c d+{-# INLINE qiNoSimpl #-}+ -- Simplify @b √c@ before constructing a 'QI'. simplifyReduceCons :: Integer -> Integer -> Integer -> Integer -> QI simplifyReduceCons a b (nonZero "simplifyReduceCons" -> c) d@@ -108,6 +151,9 @@ -- | Given @a@, @b@ and @c@ such that @n = a + b √c@, constuct a 'QI' -- corresponding to @n@.+--+-- >>> qi' 0.5 0.7 2+-- qi 5 7 2 10 qi' :: Rational -- ^ a -> Rational -- ^ b -> Integer -- ^ c@@ -120,116 +166,253 @@ (bN, bD) = (numerator b, denominator b) {-# INLINE qi' #-} --- | Given a 'QI' corresponding to @n = (a + b √c)/d@, modify @(a, b, d)@.--- Avoids having to simplify @b √c@.-qiModify :: QI- -> (Integer -> Integer -> Integer -> (Integer, Integer, Integer))- -> QI-qiModify (QI a b c d) f = reduceCons a' b' c d'- where (a', b', d') = f a b d-{-# INLINE qiModify #-}- -- | Given @n@ and @f@ such that @n = (a + b √c)/d@, run @f a b c d@.+--+-- >>> runQI (qi 3 4 5 6) (\a b c d -> (a,b,c,d))+-- (3,4,5,6) runQI :: QI -> (Integer -> Integer -> Integer -> Integer -> a) -> a runQI (QI a b c d) f = f a b c d {-# INLINE runQI #-} -- | Given @n@ and @f@ such that @n = a + b √c@, run @f a b c@.+--+-- >>> runQI' (qi' 0.5 0.7 2) (\a b c -> (a, b, c))+-- (1 % 2,7 % 10,2) runQI' :: QI -> (Rational -> Rational -> Integer -> a) -> a runQI' (QI a b c d) f = f (a % d) (b % d) c {-# INLINE runQI' #-} -- | Given @n@ such that @n = (a + b √c)/d@, return @(a, b, c, d)@.+--+-- >>> unQI (qi 3 4 5 6)+-- (3,4,5,6) unQI :: QI -> (Integer, Integer, Integer, Integer) unQI n = runQI n (,,,) {-# INLINE unQI #-} -- | Given @n@ such that @n = a + b √c@, return @(a, b, c)@.+--+-- >>> unQI' (qi' 0.5 0.7 2)+-- (1 % 2,7 % 10,2) unQI' :: QI -> (Rational, Rational, Integer) unQI' n = runQI' n (,,) {-# INLINE unQI' #-} --- | The constant zero. @qi 0 0 0 1@+-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @(a, b, c, d)@.+--+-- >>> view _qi (qi 3 4 5 6)+-- (3,4,5,6)+--+-- >>> over _qi (\(a,b,c,d) -> (a+10, b+10, c+10, d+10)) (qi 3 4 5 6)+-- qi 13 14 15 16+_qi :: Lens' QI (Integer, Integer, Integer, Integer)+_qi f n = (\ ~(a',b',c',d') -> qi a' b' c' d') <$> f (unQI n)+{-# INLINE _qi #-}++-- | Given a 'QI' corresponding to @n = a + b √c@, access @(a, b, c)@.+--+-- >>> view _qi' (qi' 0.5 0.7 2)+-- (1 % 2,7 % 10,2)+--+-- >>> over _qi' (\(a,b,c) -> (a/5, b/6, c*3)) (qi 3 4 5 6)+-- qi 9 10 15 90+_qi' :: Lens' QI (Rational, Rational, Integer)+_qi' f n = (\ ~(a',b',c') -> qi' a' b' c') <$> f (unQI' n)+{-# INLINE _qi' #-}++-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @(a, b, d)@.+-- Avoids having to simplify @b √c@ upon reconstruction.+--+-- >>> view _qiABD (qi 3 4 5 6)+-- (3,4,6)+--+-- >>> over _qiABD (\(a,b,d) -> (a+10, b+10, d+10)) (qi 3 4 5 6)+-- qi 13 14 5 16+_qiABD :: Lens' QI (Integer, Integer, Integer)+_qiABD f (unQI -> ~(a,b,c,d)) =+ (\ ~(a',b',d') -> qiNoSimpl a' b' c d') <$> f (a,b,d)+{-# INLINE _qiABD #-}++-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @a@. It is more+-- efficient to use '_qi' or '_qiABD' when modifying multiple terms at once.+--+-- >>> view _qiA (qi 3 4 5 6)+-- 3+--+-- >>> over _qiA (+ 10) (qi 3 4 5 6)+-- qi 13 4 5 6+_qiA :: Lens' QI Integer+_qiA = _qiABD . go+ where go f ~(a,b,d) = (\a' -> (a',b,d)) <$> f a++-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @b@. It is more+-- efficient to use '_qi' or '_qiABD' when modifying multiple terms at once.+--+-- >>> view _qiB (qi 3 4 5 6)+-- 4+--+-- >>> over _qiB (+ 10) (qi 3 4 5 6)+-- qi 3 14 5 6+_qiB :: Lens' QI Integer+_qiB = _qiABD . go+ where go f ~(a,b,d) = (\b' -> (a,b',d)) <$> f b++-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @c@. It is more+-- efficient to use '_qi' or '_qiABD' when modifying multiple terms at once.+--+-- >>> view _qiC (qi 3 4 5 6)+-- 5+--+-- >>> over _qiC (+ 10) (qi 3 4 5 6)+-- qi 3 4 15 6+_qiC :: Lens' QI Integer+_qiC = _qi . go+ where go f ~(a,b,c,d) = (\c' -> (a,b,c',d)) <$> f c++-- | Given a 'QI' corresponding to @n = (a + b √c)/d@, access @d@. It is more+-- efficient to use '_qi' or '_qiABD' when modifying multiple terms at once.+--+-- >>> view _qiD (qi 3 4 5 6)+-- 6+--+-- >>> over _qiD (+ 10) (qi 3 4 5 6)+-- qi 3 4 5 16+_qiD :: Lens' QI Integer+_qiD = _qiABD . go+ where go f ~(a,b,d) = (\d' -> (a,b,d')) <$> f d++-- | The constant zero.+--+-- >>> qiZero+-- qi 0 0 0 1 qiZero :: QI qiZero = qi 0 0 0 1 {-# INLINE qiZero #-} --- | The constant one. @qi 1 0 0 1@+-- | The constant one.+--+-- >>> qiOne+-- qi 1 0 0 1 qiOne :: QI qiOne = qi 1 0 0 1 {-# INLINE qiOne #-} -- | Check if the value is zero.+--+-- >>> map qiIsZero [qiZero, qiOne, qiSubR (qi 7 0 0 2) 3.5]+-- [True,False,True] qiIsZero :: QI -> Bool -- If b = 0 then c = 0 and vice versa, guaranteed by the constructor. qiIsZero (unQI -> ~(a,b,_,_)) = a == 0 && b == 0 {-# INLINE qiIsZero #-} -- | Convert a 'QI' number into a 'Floating' one.+--+-- >>> qiToFloat (qi 3 4 5 6) == ((3 + 4 * sqrt 5)/6 :: Double)+-- True qiToFloat :: Floating a => QI -> a qiToFloat (unQI -> ~(a,b,c,d)) = (fromInteger a + fromInteger b * sqrt (fromInteger c)) / fromInteger d {-# INLINE qiToFloat #-} -- | Add an 'Integer' to a 'QI'.+--+-- >>> qi 3 4 5 6 `qiAddI` 1+-- qi 9 4 5 6 qiAddI :: QI -> Integer -> QI-qiAddI n x = qiModify n $ \a b d ->- a `seq` b `seq` d `seq` x `seq` (a + d*x, b, d)+qiAddI n x = over _qiABD go n+ where go ~(a,b,d) = a `seq` b `seq` d `seq` x `seq` (a + d*x, b, d) {-# INLINE qiAddI #-} -- | Add a 'Rational' to a 'QI'.+--+-- >>> qi 3 4 5 6 `qiAddR` 1.2+-- qi 51 20 5 30 qiAddR :: QI -> Rational -> QI-qiAddR n x = qiModify n $ \a b d ->- -- n = (a + b √c)/d + xN/xD- -- n = ((a + b √c) xD)/(d xD) + (d xN)/(d xD)- -- n = ((a xD + d xN) + b xD √c)/(d xD)- a `seq` b `seq` d `seq` xN `seq` xD `seq` (a*xD + d*xN, b*xD, d*xD)- where (xN, xD) = (numerator x, denominator x)+qiAddR n x = over _qiABD go n+ where+ -- n = (a + b √c)/d + xN/xD+ -- n = ((a + b √c) xD)/(d xD) + (d xN)/(d xD)+ -- n = ((a xD + d xN) + b xD √c)/(d xD)+ go ~(a,b,d) =+ a `seq` b `seq` d `seq` xN `seq` xD `seq` (a*xD + d*xN, b*xD, d*xD)+ (xN, xD) = (numerator x, denominator x) {-# INLINE qiAddR #-} -- | Subtract an 'Integer' from a 'QI'.+--+-- >>> qi 3 4 5 6 `qiSubI` 1+-- qi (-3) 4 5 6 qiSubI :: QI -> Integer -> QI qiSubI n x = qiAddI n (negate x) {-# INLINE qiSubI #-} -- | Subtract a 'Rational' from a 'QI'.+--+-- >>> qi 3 4 5 6 `qiSubR` 1.2+-- qi (-21) 20 5 30 qiSubR :: QI -> Rational -> QI qiSubR n x = qiAddR n (negate x) {-# INLINE qiSubR #-} -- | Multiply a 'QI' by an 'Integer'.+--+-- >>> qi 3 4 5 6 `qiMulI` 2+-- qi 3 4 5 3 qiMulI :: QI -> Integer -> QI-qiMulI n x = qiModify n $ \a b d ->- a `seq` b `seq` d `seq` x `seq` (a*x, b*x, d)+qiMulI n x = over _qiABD go n+ where go ~(a,b,d) = a `seq` b `seq` d `seq` x `seq` (a*x, b*x, d) {-# INLINE qiMulI #-} -- | Multiply a 'QI' by a 'Rational'.+--+-- >>> qi 3 4 5 6 `qiMulR` 0.5+-- qi 3 4 5 12 qiMulR :: QI -> Rational -> QI-qiMulR n x = qiModify n $ \a b d ->- -- n = (a + b √c)/d xN/xD- -- n = (a xN + b xN √c)/(d xD)- a `seq` b `seq` d `seq` xN `seq` xD `seq` (a*xN, b*xN, d*xD)- where (xN, xD) = (numerator x, denominator x)+qiMulR n x = over _qiABD go n+ where+ -- n = (a + b √c)/d xN/xD+ -- n = (a xN + b xN √c)/(d xD)+ go ~(a,b,d) = a `seq` b `seq` d `seq` xN `seq` xD `seq` (a*xN, b*xN, d*xD)+ (xN, xD) = (numerator x, denominator x) {-# INLINE qiMulR #-} -- | Divice a 'QI' by an 'Integer'.+--+-- >>> qi 3 4 5 6 `qiDivI` 2+-- qi 3 4 5 12 qiDivI :: QI -> Integer -> QI-qiDivI n (nonZero "qiDivI" -> x) = qiModify n $ \a b d ->- a `seq` b `seq` d `seq` x `seq` (a, b, d*x)+qiDivI n (nonZero "qiDivI" -> x) = over _qiABD go n+ where go ~(a,b,d) = a `seq` b `seq` d `seq` x `seq` (a, b, d*x) {-# INLINE qiDivI #-} -- | Divice a 'QI' by a 'Rational'.+--+-- >>> qi 3 4 5 6 `qiDivR` 0.5+-- qi 3 4 5 3 qiDivR :: QI -> Rational -> QI qiDivR n (nonZero "qiDivR" -> x) = qiMulR n (recip x) {-# INLINE qiDivR #-} -- | Negate a 'QI'.+--+-- >>> qiNegate (qi 3 4 5 6)+-- qi (-3) (-4) 5 6 qiNegate :: QI -> QI-qiNegate n = qiModify n $ \a b d ->- a `seq` b `seq` d `seq` (negate a, negate b, d)+qiNegate n = over _qiABD go n+ where go ~(a,b,d) = a `seq` b `seq` d `seq` (negate a, negate b, d) {-# INLINE qiNegate #-} -- | Compute the reciprocal of a 'QI'.+--+-- >>> qiRecip (qi 5 0 0 2)+-- Just (qi 2 0 0 5)+--+-- >>> qiRecip (qi 0 1 5 2)+-- Just (qi 0 2 5 5)+--+-- >>> qiRecip qiZero+-- Nothing qiRecip :: QI -> Maybe QI qiRecip n@(unQI -> ~(a,b,c,d)) -- 1/((a + b √c)/d) =@@ -239,25 +422,55 @@ -- (a d − b d √c) / (a² − b² c) | qiIsZero n = Nothing | denom == 0 = error ("qiRecip: Failed for " ++ show n)- | otherwise = Just (qiModify n (\_ _ _ -> (a * d, negate (b * d), denom)))+ | otherwise = Just (set _qiABD (a * d, negate (b * d), denom) n) where denom = (a*a - b*b * c) -- | Add two 'QI's if the square root terms are the same or zeros.+--+-- >>> qi 3 4 5 6 `qiAdd` qiOne+-- Just (qi 9 4 5 6)+--+-- >>> qi 3 4 5 6 `qiAdd` qi 3 4 5 6+-- Just (qi 3 4 5 3)+--+-- >>> qi 0 1 5 1 `qiAdd` qi 0 1 6 1+-- Nothing qiAdd :: QI -> QI -> Maybe QI qiAdd n@(unQI -> ~(a,b,c,d)) n'@(unQI -> ~(a',b',c',d')) -- n = (a + b √c)/d + (a' + b' √c')/d' -- n = ((a + b √c) d' + (a' + b' √c') d)/(d d') -- if c = c' then n = ((a d' + a' d) + (b d' + b' d) √c)/(d d')- | c == 0 = Just (qiModify n' (\_ _ _ -> (a*d' + a'*d, b'*d, d*d')))- | c' == 0 = Just (qiModify n (\_ _ _ -> (a*d' + a'*d, b*d' , d*d')))- | c == c' = Just (qiModify n (\_ _ _ -> (a*d' + a'*d, b*d' + b'*d, d*d')))+ | c == 0 = Just (set _qiABD (a*d' + a'*d, b'*d, d*d') n')+ | c' == 0 = Just (set _qiABD (a*d' + a'*d, b*d' , d*d') n)+ | c == c' = Just (set _qiABD (a*d' + a'*d, b*d' + b'*d, d*d') n) | otherwise = Nothing -- | Subtract two 'QI's if the square root terms are the same or zeros.+--+-- >>> qi 3 4 5 6 `qiSub` qiOne+-- Just (qi (-3) 4 5 6)+--+-- >>> qi 3 4 5 6 `qiSub` qi 3 4 5 6+-- Just (qi 0 0 0 1)+--+-- >>> qi 0 1 5 1 `qiSub` qi 0 1 6 1+-- Nothing qiSub :: QI -> QI -> Maybe QI qiSub n n' = qiAdd n (qiNegate n') -- | Multiply two 'QI's if the square root terms are the same or zeros.+--+-- >>> qi 3 4 5 6 `qiMul` qiZero+-- Just (qi 0 0 0 1)+--+-- >>> qi 3 4 5 6 `qiMul` qiOne+-- Just (qi 3 4 5 6)+--+-- >>> qi 3 4 5 6 `qiMul` qi 3 4 5 6+-- Just (qi 89 24 5 36)+--+-- >>> qi 0 1 5 1 `qiMul` qi 0 1 6 1+-- Nothing qiMul :: QI -> QI -> Maybe QI qiMul n@(unQI -> ~(a,b,c,d)) n'@(unQI -> ~(a',b',c',d')) -- n = (a + b √c)/d (a' + b' √c')/d'@@ -265,16 +478,40 @@ -- if c = 0 then n = (a a' + a b' √c')/(d d') -- if c' = 0 then n = (a a' + a' b √c)/(d d') -- if c = c' then n = ((a a' + b b' c) + (a b' + a' b) √c)/(d d')- | c == 0 = Just (qiModify n' (\_ _ _ -> (a*a' , a*b' , d*d')))- | c' == 0 = Just (qiModify n (\_ _ _ -> (a*a' , a'*b, d*d')))- | c == c' = Just (qiModify n (\_ _ _ -> (a*a' + b*b'*c, a*b' + a'*b, d*d')))+ | c == 0 = Just (set _qiABD (a*a' , a*b' , d*d') n')+ | c' == 0 = Just (set _qiABD (a*a' , a'*b, d*d') n)+ | c == c' = Just (set _qiABD (a*a' + b*b'*c, a*b' + a'*b, d*d') n) | otherwise = Nothing -- | Divide two 'QI's if the square root terms are the same or zeros.+--+-- >>> qi 3 4 5 6 `qiDiv` qiZero+-- Nothing+--+-- >>> qi 3 4 5 6 `qiDiv` qiOne+-- Just (qi 3 4 5 6)+--+-- >>> qi 3 4 5 6 `qiDiv` qi 3 4 5 6+-- Just (qi 1 0 0 1)+--+-- >>> qi 3 4 5 6 `qiDiv` qi 0 1 5 1+-- Just (qi 20 3 5 30)+--+-- >>> qi 0 1 5 1 `qiDiv` qi 0 1 6 1+-- Nothing qiDiv :: QI -> QI -> Maybe QI qiDiv n n' = qiMul n =<< qiRecip n' -- | Exponentiate a 'QI' to an 'Integer' power.+--+-- >>> qi 3 4 5 6 `qiPow` 0+-- qi 1 0 0 1+--+-- >>> qi 3 4 5 6 `qiPow` 1+-- qi 3 4 5 6+--+-- >>> qi 3 4 5 6 `qiPow` 2+-- qi 89 24 5 36 qiPow :: QI -> Integer -> QI qiPow num (nonNegative "qiPow" -> pow) = go num pow where@@ -295,6 +532,15 @@ sudoQIMul n n' = case qiMul n n' of ~(Just m) -> m -- | Compute the floor of a 'QI'.+--+-- >>> qiFloor (qi 10 0 0 2)+-- 5+--+-- >>> qiFloor (qi 10 2 2 2)+-- 6+--+-- >>> qiFloor (qi 10 2 5 2)+-- 7 qiFloor :: QI -> Integer qiFloor (unQI -> ~(a,b,c,d)) = -- n = (a + b √c)/d@@ -307,6 +553,19 @@ ~(b2cLow, b2cHigh) = iSqrtBounds (b*b * c) -- | Convert a (possibly periodic) continued fraction to a 'QI'.+--+-- @[2; 2] = 2 + 1\/2 = 5\/2@.+--+-- >>> continuedFractionToQI (2,NonCyc [2])+-- qi 5 0 0 2+--+-- @[2; 1, 1, 1, 4, 1, 1, 1, 4, …] = √7@.+--+-- >>> continuedFractionToQI (2,Cyc [] 1 [1,1,4])+-- qi 0 1 7 1+--+-- >>> continuedFractionToQI (0,Cyc [83,78,65,75,69] 32 [66,65,68,71,69,82])+-- qi 987601513930253257378987883 1 14116473325908285531353005 81983584717737887813195873886 continuedFractionToQI :: (Integer, CycList Integer) -> QI continuedFractionToQI (i0_, is_) = qiAddI (go is_) i0_ where@@ -350,6 +609,19 @@ pos = positive "continuedFractionToQI" -- | Convert a 'QI' into a (possibly periodic) continued fraction.+--+-- @5\/2 = 2 + 1\/2 = [2; 2]@.+--+-- >>> qiToContinuedFraction (qi 5 0 0 2)+-- (2,NonCyc [2])+--+-- @√7 = [2; 1, 1, 1, 4, 1, 1, 1, 4, …]@.+--+-- >>> qiToContinuedFraction (qi 0 1 7 1)+-- (2,Cyc [] 1 [1,1,4])+--+-- >>> qiToContinuedFraction (qi 987601513930253257378987883 1 14116473325908285531353005 81983584717737887813195873886)+-- (0,Cyc [83,78,65,75,69] 32 [66,65,68,71,69,82]) qiToContinuedFraction :: QI -> (Integer, CycList Integer) qiToContinuedFraction num
src/Numeric/QuadraticIrrational/CyclicList.hs view
@@ -14,6 +14,16 @@ import Data.Foldable import Data.Monoid +-- $setup+-- import Data.Foldable (toList)++-- | A container for a possibly cyclic list.+--+-- >>> toList (NonCyc "hello")+-- "hello"+--+-- >>> take 70 (toList (Cyc "prefix " 'c' "ycle"))+-- "prefix cyclecyclecyclecyclecyclecyclecyclecyclecyclecyclecyclecyclecyc" data CycList a = NonCyc [a] -- ^ A non-cyclic list. | Cyc [a] a [a] -- ^ A non-cyclic list followed by the head of a cyclic list
+ src/Numeric/QuadraticIrrational/Internal/Lens.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE Rank2Types #-}++-- |+-- Module : Numeric.QuadraticIrrational.Internal.Lens+-- Description : A tiny implementation of some lens primitives+-- Copyright : © 2014 Johan Kiviniemi+-- License : MIT+-- Maintainer : Johan Kiviniemi <devel@johan.kiviniemi.name>+-- Stability : provisional+-- Portability : Rank2Types+--+-- A tiny implementation of some lens primitives. Please see+-- <http://hackage.haskell.org/package/lens> for proper documentation.++module Numeric.QuadraticIrrational.Internal.Lens+ ( Lens, Traversal, Lens', Traversal', Getting, Setting+ , view, over, set+ ) where++import Control.Applicative+import Data.Functor.Identity++type Lens s t a b = Functor f => (a -> f b) -> s -> f t+type Traversal s t a b = Applicative f => (a -> f b) -> s -> f t++type Lens' s a = Functor f => (a -> f a) -> s -> f s+type Traversal' s a = Applicative f => (a -> f a) -> s -> f s++type Getting r s a = (a -> Const r a) -> s -> Const r s+type Setting s t a b = (a -> Identity b) -> s -> Identity t++view :: Getting a s a -> s -> a+view l s = getConst (l Const s)+{-# INLINE view #-}++over :: Setting s t a b -> (a -> b) -> s -> t+over l f s = runIdentity (l (f `seq` Identity . f) s)+{-# INLINE over #-}++set :: Setting s t a b -> b -> s -> t+set l b s = over l (const b) s+{-# INLINE set #-}
− tests/Main.hs
@@ -1,16 +0,0 @@-module Main (main) where--import Test.Tasty--import qualified CyclicList-import qualified QuadraticIrrational--main :: IO ()-main = defaultMain tests--tests :: TestTree-tests =- testGroup "quadratic-irrational"- [ CyclicList.tests- , QuadraticIrrational.tests- ]
tests/QuadraticIrrational.hs view
@@ -10,6 +10,7 @@ import Test.Tasty.QuickCheck import Numeric.QuadraticIrrational+import Numeric.QuadraticIrrational.Internal.Lens -- Slow but precise. type RefFloat = CReal@@ -44,19 +45,48 @@ , testProperty "qi'/runQI'" $ \a b (NonNegative c) -> runQI' (qi' a b c) $ \a' b' c' -> approxEq' (approxQI' a b c) (approxQI' a' b' c')+ ]+ , testGroup "Lenses"+ [ testProperty "_qi" $ \n a' b' (NonNegative c') (NonZero d') ->+ let n' = over _qi (\(a,b,c,d) -> (a+a',b-b',c*c',d*d')) n+ n'' = runQI n $ \a b c d -> qi (a+a') (b-b') (c*c') (d*d')+ in approxEq (qiToFloat n') (qiToFloat n'') - , testProperty "qiModify" $ \n a' b' (NonZero d') ->- runQI n $ \a b c d ->- approxEq' (qiToFloat (qiModify n (\a_ b_ d_ ->- (a_+a', b_-b', d_*d'))))- (qiToFloat (qi (a+a') (b-b') c (d*d')))+ , testProperty "_qi'" $ \n a' b' (NonNegative c') ->+ let n' = over _qi' (\(a,b,c) -> (a+a',b-b',c*c')) n+ n'' = runQI' n $ \a b c -> qi' (a+a') (b-b') (c*c')+ in approxEq (qiToFloat n') (qiToFloat n'') - , testProperty "qiToFloat" $ \a b (NonNegative c) (NonZero d) ->- approxEq' (qiToFloat (qi a b c d)) (approxQI a b c d)- ]+ , testProperty "_qiABD" $ \n a' b' (NonZero d') ->+ let n' = over _qiABD (\(a,b,d) -> (a+a',b-b',d*d')) n+ n'' = runQI n $ \a b c d -> qi (a+a') (b-b') c (d*d')+ in approxEq (qiToFloat n') (qiToFloat n'') + , testProperty "_qiA" $ \n a' ->+ let n' = over _qiA (+ a') n+ n'' = runQI n $ \a b c d -> qi (a+a') b c d+ in approxEq (qiToFloat n') (qiToFloat n'')++ , testProperty "_qiB" $ \n b' ->+ let n' = over _qiB (+ b') n+ n'' = runQI n $ \a b c d -> qi a (b+b') c d+ in approxEq (qiToFloat n') (qiToFloat n'')++ , testProperty "_qiC" $ \n (NonNegative c') ->+ let n' = over _qiC (* c') n+ n'' = runQI n $ \a b c d -> qi a b (c*c') d+ in approxEq (qiToFloat n') (qiToFloat n'')++ , testProperty "_qiD" $ \n (NonZero d') ->+ let n' = over _qiD (* d') n+ n'' = runQI n $ \a b c d -> qi a b c (d*d')+ in approxEq (qiToFloat n') (qiToFloat n'')+ ] , testGroup "Numerical operations"- [ testProperty "qiAddI" $ \n x ->+ [ testProperty "qiToFloat" $ \a b (NonNegative c) (NonZero d) ->+ approxEq' (qiToFloat (qi a b c d)) (approxQI a b c d)++ , testProperty "qiAddI" $ \n x -> approxEq' (qiToFloat (qiAddI n x)) (qiToFloat n + fromInteger x) , testProperty "qiSubI" $ \n x ->@@ -116,8 +146,9 @@ , testProperty "qiFloor" $ \n -> qiFloor n === floor (qiToFloat n :: RefFloat)-- , testProperty "qiToContinuedFraction/continuedFractionToQI" $ \n ->+ ]+ , testGroup "Continued fractions"+ [ testProperty "qiToContinuedFraction/continuedFractionToQI" $ \n -> let cf = qiToContinuedFraction n len = case cf of (_, NonCyc _) -> 0
+ tests/doctests.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE MultiWayIf #-}++module Main (main) where++import Control.Applicative+import Control.Monad+import Control.Monad.List+import Data.List+import System.Directory+import System.FilePath+import Test.DocTest++main :: IO ()+main = do+ sources <- findSources "src"+ doctest ("-isrc" : sources)++findSources :: FilePath -> IO [FilePath]+findSources dir = runListT (goDir dir)+ where+ goItem :: FilePath -> FilePath -> ListT IO FilePath+ goItem _ ('.':_) = empty+ goItem parent name = do+ let path = parent </> name+ isDir <- liftIO (doesDirectoryExist path)+ isFile <- liftIO (doesFileExist path)+ if | isDir -> goDir path+ | isFile -> goFile path+ | otherwise -> empty++ goDir path = goItem path =<< ListT (getDirectoryContents path)+ goFile path = path <$ guard (".hs" `isSuffixOf` path)
+ tests/tasty.hs view
@@ -0,0 +1,16 @@+module Main (main) where++import Test.Tasty++import qualified CyclicList+import qualified QuadraticIrrational++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests =+ testGroup "quadratic-irrational"+ [ CyclicList.tests+ , QuadraticIrrational.tests+ ]