packages feed

Decimal 0.3.1 → 0.5.2

raw patch · 6 files changed

Files

Decimal.cabal view
@@ -1,24 +1,24 @@ Name:                Decimal-Version:             0.3.1+Version:             0.5.2 License:             BSD3 License-file:        LICENSE.txt-Copyright:           Paul Johnson, 2013+Copyright:           Paul Johnson, 2013, 2018, 2021. Author:              Paul Johnson Maintainer:          paul@cogito.org.uk Stability:           beta Category:            Math-Cabal-version:       >=1.10+Cabal-version:       1.18 Build-type:          Simple Synopsis:            Decimal numbers with variable precision Description:         A decimal number has an integer mantissa and a negative                     exponent.  The exponent can be interpreted as the number                     of decimal places in the value.-Extra-source-files:  README.txt-tested-with:         GHC==7.4.2+tested-with:         GHC==8.2.2, GHC==8.10.4 homepage:            https://github.com/PaulJohnson/Haskell-Decimal+extra-doc-files:     LICENSE.txt, README.md, changelog.md -library -  build-depends:    +library+  build-depends:                     base >= 4 && < 5,                     deepseq   hs-source-dirs:   src@@ -30,7 +30,7 @@ test-suite Main   type:            exitcode-stdio-1.0   x-uses-tf:       true-  build-depends:   +  build-depends:                    base >= 4 && < 5,                    HUnit >= 1.2 && < 2,                    QuickCheck >= 2.4,@@ -45,4 +45,4 @@   --      default-language: Haskell2010   default-language: Haskell2010   main-is:         Main.hs-+  other-modules:   Data.Decimal
+ README.md view
@@ -0,0 +1,32 @@+Haskell-Decimal+===============++Fixed-precision decimal numbers, where the precision is carried with the numbers at run-time.++The `Decimal` type is mainly intended for doing financial arithmetic+where the number of decimal places may not be known at compile time+(e.g. for a program that handles both Yen and Dollars) and the+application must not drop pennies on the floor.  For instance if you+have to divide $10 between three people then one of them has to get+$3.34.++The number of decimal places in a value is represented as a Word8,+allowing for up to 255 decimal places.  Functions preserve precision.+Addition and subtraction operators return a result with the precision of the most+precise argument, so 2.3 + 5.678 = 7.978. Multiplication and division use whatever precision is+necessary up to 255 decimal places.+++QuickCheck Specification+------------------------++Data.Decimal includes a set of QuickCheck properties which act as both+tests and a formal specification. To run the tests do:++   cabal configure --enable-tests+   cabal build+   cabal test++or++   stack test
− README.txt
@@ -1,59 +0,0 @@-Variable Precision Decimal Numbers-==================================--The "Decimal" type is mainly intended for doing financial arithmetic-where the number of decimal places may not be known at compile time-(e.g. for a program that handles both Yen and Dollars) and the-application must not drop pennies on the floor.  For instance if you-have to divide $10 between three people then one of them has to get-$3.34.--The number of decimal places in a value is represented as a Word8,-allowing for up to 255 decimal places.  Functions preserve precision.-Binary operators return a result with the precision of the most-precise argument, so 2.3 + 5.678 = 7.978.--If you need fixed precision decimal arithmetic where the precision is-known at compile time then Data.Number.Fixed from Lennart Augustsson's-"numbers" package is more likely to be what you want.--QuickCheck Specification---------------------------Data.Decimal includes a set of QuickCheck properties which act as both-tests and a formal specification. To run the tests do:--   cabal configure --enable-tests-   cabal build-   cabal test--Data.Decimal is an instance of Arbitrary, for your convenience in-writing your own tests.---Version 0.2.1----------------Fixed "base" dependency.-Put test suite under "cabal test"--Version 0.2.2----------------Minor fixes to allow compilation under other versions of GHC.--Version 0.2.3----------------Added instance of NFData from Control.DeepSeq, and hence a dependency-on the deepseq package, thanks to Jeff Shaw (shawjef3 at msu.edu).--Version 0.3.1----------------Added Typeable, Fractional and RealFrac instances.-Multiplication now returns an exact result, increasing precision if necessary.--These changes alter the API. Hence the increment to the major version number.--Thanks to Alexey Uimanov (s9gf4ult at gmail.com).
+ changelog.md view
@@ -0,0 +1,58 @@+Version 0.2.1+-------------++* Fixed `base` dependency.++* Put test suite under `cabal test`++Version 0.2.2+-------------++* Minor fixes to allow compilation under other versions of GHC.++Version 0.2.3+-------------++* Added instance of `NFData` from `Control.DeepSeq`, and hence a dependency+on the `deepseq` package, thanks to Jeff Shaw (shawjef3 at msu.edu).++Version 0.3.1+-------------++* Added `Typeable`, `Fractional` and `RealFrac` instances.++* Multiplication now returns an exact result, increasing precision if necessary.++These changes alter the API. Hence the increment to the major version number.++Thanks to Alexey Uimanov (s9gf4ult at gmail.com).++Version 0.4.1+-------------++* Improved `Read` instance. Now handles `"1.2e3"` and `reads` only returns a single parse.++* Corrected documentation.++* Added `Enum` instance.++* `decimalConvert` now returns a Maybe value. The old version has been renamed+to "unsafeDecimalConvert.++Version 0.5.1+-------------++* Bankers' Rounding implemented in "roundTo". This rounds values ending in "5" to+the nearest even number, in line with the behaviour of "Prelude.round". This+is potentially a breaking change for software that depends on the old+behavior, so the minor version number has been bumped.++* Added a `stack.yaml` file.++* Corrected documentation.++* `Read` instance now handles leading spaces properly.++* Fixed compiler warnings in test suite.++* Added `roundTo'` which allows for `truncate`, `floor` and `ceiling` behaviour when rounding.
src/Data/Decimal.hs view
@@ -3,37 +3,52 @@ -- | Decimal numbers are represented as @m*10^(-e)@ where -- @m@ and @e@ are integers.  The exponent @e@ is an unsigned Word8.  Hence -- the smallest value that can be represented is @10^-255@.--- --- Unary arithmetic results have the exponent of the argument.  Binary--- arithmetic results have an exponent equal to the maximum of the exponents--- of the arguments.--- --- Decimal numbers are defined as instances of @Real@.  This means that--- conventional division is not defined.  Instead the functions @divide@ and --- @allocate@ will split a decimal amount into lists of results.  These --- results are guaranteed to sum to the original number.  This is a useful--- property when doing financial arithmetic.--- +--+-- Unary arithmetic results have the exponent of the argument.+-- Addition and subtraction results have an exponent equal to the+-- maximum of the exponents of the arguments. Other operators have+-- exponents sufficient to show the exact result, up to a limit of+-- 255:+--+-- > 0.15 * 0.15 :: Decimal    = 0.0225+-- > (1/3) :: Decimal          = 0.33333333333333...+-- > decimalPlaces (1/3)       = 255+--+-- While @(/)@ is defined, you don't normally want to use it. Instead+-- The functions "divide" and "allocate" will split a decimal amount+-- into lists of results which are guaranteed to sum to the original+-- number.  This is a useful property when doing financial arithmetic.+-- -- The arithmetic on mantissas is always done using @Integer@, regardless of--- the type of @DecimalRaw@ being manipulated.  In practice it is recommended--- that @Decimal@ be used, with other types being used only where necessary--- (e.g. to conform to a network protocol).+-- the type of @DecimalRaw@ being manipulated.  In practice it is strongly+-- recommended that @Decimal@ be used, with other types being used only where+-- necessary (e.g. to conform to a network protocol). For instance+-- @(1/3) :: DecimalRaw Int@ does not give the right answer.+--+-- Care must be taken with literal values of type Decimal. As per the Haskell+-- Report, the literal @10.00@ will be converted into @fromRational 10.00@, which+-- in a @Decimal@ context will be converted into @10@ with zero decimal places.+-- Likewise @10.10@ will be converted into @10.1@ with one decimal place. If+-- you mean @10.00@ with 2 decimal places then you have to write @roundTo 2 10@. + module Data.Decimal (    -- ** Decimal Values    DecimalRaw (..),    Decimal,    realFracToDecimal,    decimalConvert,+   unsafeDecimalConvert,    roundTo,+   roundTo',    (*.),    divide,    allocate,    eitherFromRational,-   normalizeDecimal,+   normalizeDecimal ) where -import Control.Monad.Instances ()+ import Control.DeepSeq import Data.Char import Data.Ratio@@ -44,56 +59,84 @@ -- | Raw decimal arithmetic type constructor.  A decimal value consists of an -- integer mantissa and a negative exponent which is interpreted as the number -- of decimal places.  The value stored in a @Decimal d@ is therefore equal to:--- +-- -- > decimalMantissa d / (10 ^ decimalPlaces d)--- +-- -- The "Show" instance will add trailing zeros, so @show $ Decimal 3 1500@ -- will return \"1.500\".  Conversely the "Read" instance will use the decimal -- places to determine the precision.--- --- Arithmetic and comparision operators convert their arguments to the --- greater of the two precisions, and return a result of that precision.  --- Regardless of the type of the arguments, all mantissa arithmetic is done--- using @Integer@ types, so application developers do not need to worry about--- overflow in the internal algorithms.  However the result of each operator--- will be converted to the mantissa type without checking for overflow.-data (Integral i) => DecimalRaw i = Decimal {-      decimalPlaces :: ! Word8,-      decimalMantissa :: ! i}+data DecimalRaw i = Decimal {+      decimalPlaces :: !Word8,+      decimalMantissa :: !i}                                   deriving (Typeable)  --- | Arbitrary precision decimal type.  As a rule programs should do decimal--- arithmetic with this type and only convert to other instances of --- "DecimalRaw" where required by an external interface.--- +-- | Arbitrary precision decimal type.  Programs should do decimal+-- arithmetic with this type and only convert to other instances of+-- "DecimalRaw" where required by an external interface. This will avoid+-- issues with integer overflows.+-- -- Using this type is also faster because it avoids repeated conversions -- to and from @Integer@. type Decimal = DecimalRaw Integer -instance (Integral i, NFData i) => NFData (DecimalRaw i) where+instance (NFData i) => NFData (DecimalRaw i) where     rnf (Decimal _ i) = rnf i --- | Convert a real fractional value into a Decimal of the appropriate +instance (Integral i) => Enum (DecimalRaw i) where+   succ x = x + 1+   pred x = x - 1+   toEnum = fromIntegral+   fromEnum = fromIntegral . decimalMantissa . roundTo 0+   enumFrom = iterate (+1)+   enumFromThen x1 x2 = let dx = x2 - x1 in iterate (+dx) x1+   enumFromTo x1 x2 = takeWhile (<= x2) $ iterate (+1) x1+   enumFromThenTo x1 x2 x3 = takeWhile (<= x3) $ enumFromThen x1 x2+++-- | Convert a real fractional value into a Decimal of the appropriate -- precision. realFracToDecimal :: (Integral i, RealFrac r) => Word8 -> r -> DecimalRaw i realFracToDecimal e r = Decimal e $ round (r * (10^e))  --- Internal function to divide and return the nearest integer.+-- Internal function to divide and return the nearest integer. Implements Bankers' Rounding in+-- which 0.5 is rounded to the nearest even value. This follows the practice of "Prelude.round". divRound :: (Integral a) => a -> a -> a-divRound n1 n2 = if abs r > abs (n2 `quot` 2) then n + signum n else n-    where (n, r) = n1 `quotRem` n2+divRound n1 n2 = n + bankers+    where+      (n, r) = n1 `quotRem` n2+      bankers = case compare (abs r * 2) (abs n2) of+         LT -> 0+         GT -> signum n1+         EQ -> if odd n then signum n1 else 0   -- | Convert a @DecimalRaw@ from one base representation to another.  Does--- not check for overflow in the new representation.-decimalConvert :: (Integral a, Integral b) => DecimalRaw a -> DecimalRaw b-decimalConvert (Decimal e n) = Decimal e $ fromIntegral n+-- not check for overflow in the new representation. Only use after+-- using "roundTo" to put an upper value on the exponent, or to convert+-- to a larger representation.+unsafeDecimalConvert :: (Integral a, Integral b) => DecimalRaw a -> DecimalRaw b+unsafeDecimalConvert (Decimal e n) = Decimal e $ fromIntegral n  +-- | Convert a @DecimalRaw@ from one base to another. Returns @Nothing@ if+-- this would cause arithmetic overflow.+decimalConvert :: (Integral a, Integral b, Bounded b) =>+   DecimalRaw a -> Maybe (DecimalRaw b)+decimalConvert (Decimal e n) =+   let n1 :: Integer+       n1 = fromIntegral n+       n2 = fromIntegral n   -- Of type b.+       ub = fromIntegral $ max maxBound n2  -- Can't say "maxBound :: b", so do this instead.+       lb = fromIntegral $ min minBound n2+   in if lb <= n1 && n1 <= ub then Just $ Decimal e n2 else Nothing++ -- | Round a @DecimalRaw@ to a specified number of decimal places.-roundTo :: (Integral i) => Word8 -> DecimalRaw i -> DecimalRaw Integer+-- If the value ends in @5@ then it is rounded to the nearest even value (Banker's Rounding)+roundTo :: (Integral i) => Word8 -> DecimalRaw i -> DecimalRaw i+roundTo d (Decimal _ 0) = Decimal d 0 roundTo d (Decimal e n) = Decimal d $ fromIntegral n1     where       n1 = case compare d e of@@ -103,20 +146,37 @@       divisor = 10 ^ (e-d)       multiplier = 10 ^ (d-e) +-- | Round a @DecimalRaw@ to a specified number of decimal places using the specified+-- rounding function. Typically this will be one of @floor@, @ceiling@, @truncate@ or @round@.+-- Note that @roundTo == roundTo\' round@+roundTo' :: (Integral i) => (Rational -> i) -> Word8 -> DecimalRaw i -> DecimalRaw i+roundTo' _ d (Decimal _  0) = Decimal d 0+roundTo' f d (Decimal e n) = Decimal d $ f n1+   where+      divisor = 10 ^ (e-d)+      multiplier = 10 ^ (d-e)+      n1 = case compare d e of+         LT -> toRational n / divisor+         EQ -> toRational n+         GT -> toRational n * multiplier  -- Round the two DecimalRaw values to the largest exponent.-roundMax :: (Integral i) => -            DecimalRaw i -> DecimalRaw i -> (Word8, Integer, Integer)-roundMax d1@(Decimal e1 _) d2@(Decimal e2 _) = (e, n1, n2)+roundMax :: (Integral i) => DecimalRaw i -> DecimalRaw i -> (Word8, i, i)+roundMax (Decimal _  0)   (Decimal _  0)  = (0,0,0)+roundMax (Decimal e1 n1)  (Decimal _  0)  = (e1,n1,0)+roundMax (Decimal _  0)   (Decimal e2 n2) = (e2,0,n2)+roundMax d1@(Decimal e1 n1) d2@(Decimal e2 n2)+  | e1 == e2  = (e1, n1, n2)+  | otherwise = (e, n1', n2')     where       e = max e1 e2-      (Decimal _ n1) = roundTo e d1-      (Decimal _ n2) = roundTo e d2+      (Decimal _ n1') = roundTo e d1+      (Decimal _ n2') = roundTo e d2   instance (Integral i, Show i) => Show (DecimalRaw i) where    showsPrec _ (Decimal e n)-       | e == 0     = (concat [signStr, strN] ++)+       | e == 0     = ((signStr ++ strN) ++)        | otherwise  = (concat [signStr, intPart, ".", fracPart] ++)        where          strN = show $ abs n@@ -126,16 +186,35 @@          (intPart, fracPart) = splitAt (max 1 (len - fromIntegral e)) padded  instance (Integral i, Read i) => Read (DecimalRaw i) where-    readsPrec _ = -        readP_to_S $ do-          (intPart, _) <- gather $ do-                            optional $ char '-'-                            munch1 isDigit-          fractPart    <- option "" $ do+    readsPrec _ = readP_to_S readDecimalP+++-- | Parse a Decimal value. Used for the Read instance.+readDecimalP :: (Integral i, Read i) => ReadP (DecimalRaw i)+readDecimalP = do+          skipSpaces+          s1           <- myOpt '+' $ char '-' +++ char '+'+          intPart      <- munch1 isDigit+          fractPart    <- myOpt "" $ do                             _ <- char '.'                             munch1 isDigit-          return $ Decimal (fromIntegral $ length fractPart) $ read $ -                 intPart ++ fractPart+          expPart <- myOpt 0 $ do+                            _  <- char 'e' +++ char 'E'+                            s2 <- myOpt '+' $ char '-' +++ char '+'+                            fmap (applySign s2 . strToInt) $ munch1 isDigit+          let n = applySign s1 $ strToInt $ intPart ++ fractPart+              e = length fractPart - expPart+          if e < 0+             then return $ Decimal 0 $ n * 10 ^ negate e+             else if e < 256+                then return $ Decimal (fromIntegral e) n+                else pfail+    where+       strToInt :: (Integral n) => String -> n+       strToInt = foldl (\t v -> 10 * t + v) 0 . map (fromIntegral . subtract (ord '0') . ord)+       applySign '-' v = negate v+       applySign _   v = v+       myOpt d p = p <++ return d   instance (Integral i) => Eq (DecimalRaw i) where@@ -147,12 +226,17 @@   instance (Integral i) => Num (DecimalRaw i) where+    (Decimal _ 0) + d = d+    d + (Decimal _ 0) = d     d1 + d2 = Decimal e $ fromIntegral (n1 + n2)         where (e, n1, n2) = roundMax d1 d2+    (Decimal _ 0) - (Decimal e n) = Decimal e (-n)+    d - (Decimal _ 0) = d     d1 - d2 = Decimal e $ fromIntegral (n1 - n2)         where (e, n1, n2) = roundMax d1 d2-    d1 * d2 = normalizeDecimal $ realFracToDecimal maxBound $ (toRational d1) * (toRational d2)-+    (Decimal _ 0) * _ = 0+    _ * (Decimal _ 0) = 0+    d1 * d2 = normalizeDecimal $ realFracToDecimal maxBound $ toRational d1 * toRational d2     abs (Decimal e n) = Decimal e $ abs n     signum (Decimal _ n) = fromIntegral $ signum n     fromInteger n = Decimal 0 $ fromIntegral n@@ -161,30 +245,34 @@     toRational (Decimal e n) = fromIntegral n % (10 ^ e)  instance (Integral i) => Fractional (DecimalRaw i) where-  fromRational r = normalizeDecimal $ realFracToDecimal maxBound r-  a / b = fromRational $ (toRational a) / (toRational b)+  fromRational r =+     let+        v :: Decimal+        v = normalizeDecimal $ realFracToDecimal maxBound r+     in unsafeDecimalConvert v+  a / b = fromRational $ toRational a / toRational b  instance (Integral i) => RealFrac (DecimalRaw i) where   properFraction a = (rnd, fromRational rep)     where       (rnd, rep) = properFraction $ toRational a-      -   ++ -- | Divide a @DecimalRaw@ value into one or more portions.  The portions -- will be approximately equal, and the sum of the portions is guaranteed to -- be the original value.--- +-- -- The portions are represented as a list of pairs.  The first part of each -- pair is the number of portions, and the second part is the portion value. -- Hence 10 dollars divided 3 ways will produce @[(2, 3.33), (1, 3.34)]@.-divide :: (Integral i) => DecimalRaw i -> Int -> [(Int, DecimalRaw i)]-divide (Decimal e n) d -    | d > 0 = +divide :: Decimal -> Int -> [(Int, Decimal)]+divide (Decimal e n) d+    | d > 0 =         case n `divMod` fromIntegral d of-          (result, 0) -> [(fromIntegral d, Decimal e result)]-          (result, r) -> [(fromIntegral d - fromIntegral r,-                           Decimal e result), +          (result, 0) -> [(d, Decimal e result)]+          (result, r) -> [(d - fromIntegral r,+                           Decimal e result),                           (fromIntegral r, Decimal e (result+1))]     | otherwise = error "Data.Decimal.divide: Divisor must be > 0." @@ -192,25 +280,24 @@  -- | Allocate a @DecimalRaw@ value proportionately with the values in a list. -- The allocated portions are guaranteed to add up to the original value.--- --- Some of the allocations may be zero or negative, but the sum of the list +--+-- Some of the allocations may be zero or negative, but the sum of the list -- must not be zero.  The allocation is intended to be as close as possible -- to the following:--- +-- -- > let result = allocate d parts -- > in all (== d / sum parts) $ zipWith (/) result parts-allocate :: (Integral i) => DecimalRaw i -> [Integer] -> [DecimalRaw i]+allocate :: Decimal -> [Integer] -> [Decimal] allocate (Decimal e n) ps-    | total == 0  = +    | total == 0  =         error "Data.Decimal.allocate: allocation list must not sum to zero."     | otherwise   = map (Decimal e) $ zipWith (-) ts (tail ts)     where       ts = map fst $ scanl nxt (n, total) ps-      nxt (n1, t1) p1 = (n1 - (n1 * fromIntegral p1) `zdiv` t1, -                         t1 - fromIntegral p1)+      nxt (n1, t1) p1 = (n1 - (n1 * p1) `zdiv` t1, t1 - p1)       zdiv 0 0 = 0       zdiv x y = x `divRound` y-      total = fromIntegral $ sum ps+      total = sum ps   -- | Multiply a @DecimalRaw@ by a @RealFrac@ value.@@ -246,12 +333,12 @@     (f5, done) = factorN 5 rest     e = max f2 f5     m = num * ((10^e) `div` den)-    we = if e > (fromIntegral (maxBound :: Word8)) --  FIXME: will fail if DecimalRaw changed+    we = if e > fromIntegral (maxBound :: Word8)          then Left $ show e ++ " is too big ten power to represent as Decimal"          else Right $ fromIntegral e --- | Reduce the exponent of the decimal numer to the minimal posible value-normalizeDecimal :: (Integral i) => (DecimalRaw i) -> (DecimalRaw i)+-- | Reduce the exponent of the decimal number to the minimal possible value+normalizeDecimal :: (Integral i) => DecimalRaw i -> DecimalRaw i normalizeDecimal r = case eitherFromRational $ toRational r of   Right x -> x-  Left e -> error $ "Imposible happened: " ++ e+  Left e -> error $ "Impossible happened: " ++ e
tests/Main.hs view
@@ -4,42 +4,50 @@ import Data.Ratio import Data.Word import Test.HUnit-import Control.Applicative  import Test.QuickCheck-import qualified Test.QuickCheck.Property as P import Test.Framework as TF (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2 (testProperty)  -instance (Integral i, Arbitrary i) => Arbitrary (DecimalRaw i) where-  arbitrary = Decimal <$> arbitrary <*> arbitrary-  -- arbitrary = do +-- | Newtype introduced to avoid orphan instance.+newtype TestDecRaw i = Test (DecimalRaw i) deriving Show++type TestDec = TestDecRaw Integer++instance (Integral i, Arbitrary i) => Arbitrary (TestDecRaw i) where+  arbitrary = Test <$> (Decimal <$> arbitrary <*> arbitrary)+  -- arbitrary = do   --   e <- sized (\n -> resize (n `div` 10) arbitrary) :: Gen Int   --   m <- sized (\n -> resize (n * 10) arbitrary)   --   return $ Decimal (fromIntegral $ abs e) m-      -instance (Integral i, Arbitrary i) => CoArbitrary (DecimalRaw i) where-    coarbitrary (Decimal e m) gen = variant (v:: Integer) gen++instance (Integral i, Arbitrary i) => CoArbitrary (TestDecRaw i) where+    coarbitrary (Test (Decimal e m)) = variant (v:: Integer)        where v = fromIntegral e + fromIntegral m-  + -- | "read" is the inverse of "show".--- +-- -- > read (show n) == n-prop_readShow :: Decimal -> Bool-prop_readShow d =  (read (show d)) == d+prop_readShow :: TestDec -> Bool+prop_readShow (Test d) =  read (show d) == d ++-- | "read" can handle leading spaces.+prop_readShow1 :: TestDec -> Bool+prop_readShow1 (Test d) = read (" " ++ show d) == d+ -- | Read and show preserve decimal places.--- +-- -- > decimalPlaces (read (show n)) == decimalPlaces n-prop_readShowPrecision :: Decimal -> Bool-prop_readShowPrecision d =  decimalPlaces (read (show d) :: Decimal) +prop_readShowPrecision :: TestDec -> Bool+prop_readShowPrecision (Test d) =  decimalPlaces (read (show d) :: Decimal)                             == decimalPlaces d   -- | "fromInteger" definition.--- +-- -- > decimalPlaces (fromInteger n) == 0 && -- > decimalMantissa (fromInteger n) == n prop_fromIntegerZero :: Integer -> Bool@@ -48,16 +56,16 @@   -- | Increased precision does not affect equality.--- +-- -- > decimalPlaces d < maxBound ==> roundTo (decimalPlaces d + 1) d == d-prop_increaseDecimals :: Decimal -> Property-prop_increaseDecimals d =  +prop_increaseDecimals :: TestDec -> Property+prop_increaseDecimals (Test d) =     decimalPlaces d < maxBound ==> roundTo (decimalPlaces d + 1) d == d   -- | Decreased precision can make two decimals equal, but it can never change -- their order.--- +-- -- > forAll d1, d2 :: Decimal -> legal beforeRound afterRound -- >      where -- >         beforeRound = compare d1 d2@@ -65,8 +73,8 @@ -- >         legal GT x = x `elem` [GT, EQ] -- >         legal EQ x = x `elem` [EQ] -- >         legal LT x = x `elem` [LT, EQ]-prop_decreaseDecimals :: Decimal -> Decimal -> Bool-prop_decreaseDecimals d1 d2 =  legal beforeRound afterRound+prop_decreaseDecimals :: TestDec -> TestDec -> Bool+prop_decreaseDecimals (Test d1) (Test d2) =  legal beforeRound afterRound     where       beforeRound = compare d1 d2       afterRound = compare (roundTo 0 d1) (roundTo 0 d2)@@ -74,101 +82,105 @@       legal EQ x = x `elem` [EQ]       legal LT x = x `elem` [LT, EQ] +-- | @roundTo == roundTo' round@+prop_roundTo :: TestDec -> Word8 -> Bool+prop_roundTo (Test d) e = roundTo' round e d == roundTo e d  -- | > (x + y) - y == x-prop_inverseAdd :: Decimal -> Decimal -> Bool-prop_inverseAdd x y =  (x + y) - y == x+prop_inverseAdd :: TestDec -> TestDec -> Bool+prop_inverseAdd (Test x) (Test y) =  (x + y) - y == x   -- | Multiplication is repeated addition.--- +-- -- > forall d, NonNegative i : (sum $ replicate i d) == d * fromIntegral (max i 0)-prop_repeatedAdd :: Decimal -> Word8 -> Bool-prop_repeatedAdd d i = (sum $ replicate (fromIntegral i) d) == d * fromIntegral (max i 0)+prop_repeatedAdd :: TestDec -> Word8 -> Bool+prop_repeatedAdd (Test d) i = (sum $ replicate (fromIntegral i) d) == d * fromIntegral (max i 0)   -- | Division produces the right number of parts.--- +-- -- > forall d, Positive i : (sum $ map fst $ divide d i) == i-prop_divisionParts :: Decimal -> Positive Int -> Property-prop_divisionParts d (Positive i) =  i > 0 ==> (sum $ map fst $ divide d i) == i+prop_divisionParts :: TestDec -> Positive Int -> Property+prop_divisionParts (Test d) (Positive i) =  i > 0 ==> (sum $ map fst $ divide d i) == i   -- | Division doesn't drop any units.--- +-- -- > forall d, Positive i : (sum $ map (\(n,d1) -> fromIntegral n * d1) $ divide d i) == d-prop_divisionUnits :: Decimal -> Positive Int -> Bool-prop_divisionUnits d (Positive i) = +prop_divisionUnits :: TestDec -> Positive Int -> Bool+prop_divisionUnits (Test d) (Positive i) =     (sum $ map (\(n,d1) -> fromIntegral n * d1) $ divide d i) == d   -- | Allocate produces the right number of parts.--- +-- -- > sum ps /= 0  ==>  length ps == length (allocate d ps)-prop_allocateParts :: Decimal -> [Integer] -> Property-prop_allocateParts d ps =  +prop_allocateParts :: TestDec -> [Integer] -> Property+prop_allocateParts (Test d) ps =     sum ps /= 0 ==> length ps == length (allocate d ps)   -- | Allocate doesn't drop any units.--- +-- -- >     sum ps /= 0  ==>  sum (allocate d ps) == d-prop_allocateUnits :: Decimal -> [Integer] -> Property-prop_allocateUnits d ps =+prop_allocateUnits :: TestDec -> [Integer] -> Property+prop_allocateUnits (Test d) ps =     sum ps /= 0 ==> sum (allocate d ps) == d  -- | Absolute value definition--- --- > decimalPlaces a == decimalPlaces d && +--+-- > decimalPlaces a == decimalPlaces d && -- > decimalMantissa a == abs (decimalMantissa d) -- >    where a = abs d-prop_abs :: Decimal -> Bool-prop_abs d =  decimalPlaces a == decimalPlaces d && -              decimalMantissa a == abs (decimalMantissa d)+prop_abs :: TestDec -> Bool+prop_abs (Test d) =  decimalPlaces a == decimalPlaces d &&+                     decimalMantissa a == abs (decimalMantissa d)     where a = abs d --- | Sign number defintion--- +-- | Sign number definition+-- -- > signum d == (fromInteger $ signum $ decimalMantissa d)-prop_signum :: Decimal -> Bool-prop_signum d =  signum d == (fromInteger $ signum $ decimalMantissa d)+prop_signum :: TestDec -> Bool+prop_signum (Test d) =  signum d == (fromInteger $ signum $ decimalMantissa d)  -- | The addition is valid-                 -prop_sumValid :: Decimal -> Decimal -> Property-prop_sumValid a b = (decimalPlaces a < maxBound && decimalPlaces b < maxBound) ==>++prop_sumValid :: TestDec -> TestDec -> Property+prop_sumValid (Test a) (Test b) = (decimalPlaces a < maxBound && decimalPlaces b < maxBound) ==>                     (toRational (a + b) == (toRational a) + (toRational b)) -prop_mulValid :: Decimal -> Decimal -> Property-prop_mulValid a b = ((ad + bd) < fromIntegral (maxBound :: Word8)) ==>+prop_mulValid :: TestDec -> TestDec -> Property+prop_mulValid (Test a) (Test b) = ((ad + bd) < fromIntegral (maxBound :: Word8)) ==>                     (toRational (a * b) == (toRational a) * (toRational b))   where-    ad :: Integer+    ad, bd :: Integer     ad = fromIntegral $ decimalPlaces a     bd = fromIntegral $ decimalPlaces b -prop_eitherFromRational :: Decimal -> Bool-prop_eitherFromRational d = (Right d) == (eitherFromRational $ toRational d)+prop_eitherFromRational :: TestDec -> Bool+prop_eitherFromRational (Test d) = (Right d) == (eitherFromRational $ toRational d) -prop_normalizeDecimal :: Decimal -> Bool-prop_normalizeDecimal d = d == (normalizeDecimal d)+prop_normalizeDecimal :: TestDec -> Bool+prop_normalizeDecimal (Test d) = d == (normalizeDecimal d)   -- | Division is the inverted multiplication-prop_divisionMultiplication :: Decimal -> Decimal -> Property-prop_divisionMultiplication a b = ((ad + bd) < fromIntegral (maxBound :: Word8) && a /= 0 && b /= 0) ==>-                                  (c / a == b) .&&. (c / b == a)+prop_divisionMultiplication :: TestDec -> TestDec -> Property+prop_divisionMultiplication (Test a) (Test b) =+      ((ad + bd) < fromIntegral (maxBound :: Word8) && a /= 0 && b /= 0) ==>+      (c / a == b) .&&. (c / b == a)   where     ad :: Integer     ad = fromIntegral $ decimalPlaces a     bd = fromIntegral $ decimalPlaces b     c = a * b -prop_fromRational :: Decimal -> Bool-prop_fromRational a = a == (fromRational $ toRational a)+prop_fromRational :: TestDec -> Bool+prop_fromRational (Test a) = a == (fromRational $ toRational a) -prop_properFraction :: Decimal -> Bool-prop_properFraction a = a == (fromIntegral b + d)+prop_properFraction :: TestDec -> Bool+prop_properFraction (Test a) = a == (fromIntegral b + d)   where     b :: Integer     (b, d) = properFraction a@@ -192,10 +204,12 @@ tests = [         testGroup "QuickCheck Data.Decimal" [                 testProperty "readShow"           prop_readShow,+                testProperty "readShow1"          prop_readShow1,                 testProperty "readShowPrecision"  prop_readShowPrecision,-                testProperty "fromIntegerZero"    prop_fromIntegerZero, +                testProperty "fromIntegerZero"    prop_fromIntegerZero,                 testProperty "increaseDecimals"   prop_increaseDecimals,                 testProperty "decreaseDecimals"   prop_decreaseDecimals,+                testProperty "roundTo"            prop_roundTo,                 testProperty "inverseAdd"         prop_inverseAdd,                 testProperty "repeatedAdd"        prop_repeatedAdd,                 testProperty "divisionParts"      prop_divisionParts,@@ -218,8 +232,19 @@                 testCase "100*pi to 2dp" (dec 2 31416 @=? realFracToDecimal 2 (100 * piD)),                 testCase "1.0 * pi"      (dec 1 31    @=? dec 1 10 *. piD),                 testCase "1.23 * pi"     (dec 2 386   @=? dec 2 123 *. piD),-                testCase "Decimal to DecimalRaw Int" -                                         (decimalConvert (dec 2 123) @=? dec1 2 123),-                testCase "1.234 to rational" (1234 % 1000 @=? (toRational (dec 3 1234)))+                testCase "Decimal to DecimalRaw Int"+                                         (decimalConvert (dec 2 123) @=? Just (dec1 2 123)),+                testCase "decimalConvert overflow prevention"+                                         (decimalConvert (1/3 :: Decimal) @=?+                                            (Nothing :: Maybe (DecimalRaw Int))),+                testCase "1.234 to rational" (1234 % 1000 @=? toRational (dec 3 1234)),+                testCase "fromRational (1%10) for DecimalRaw Int"  -- Fixed bug #3+                                         (let v :: DecimalRaw Int+                                              v = fromRational (1%10)+                                          in toRational v @=? 1%10),+                testCase "Bankers rounding up"+                                         (roundTo 1 (dec 2 115) @=? dec 1 12),+                testCase "Bankers rounding down"+                                         (roundTo 1 (dec 2 125) @=? dec 1 12)                 ]        ]