packages feed

scientific 0.3.8.1 → 0.3.9.0

raw patch · 4 files changed

+213/−158 lines, 4 filesdep −smallcheckdep −tasty-smallcheckPVP ok

version bump matches the API change (PVP)

Dependencies removed: smallcheck, tasty-smallcheck

API changes (from Hackage documentation)

Files

changelog view
@@ -1,3 +1,12 @@+0.3.9.0++	* Reimplement most internal functions which used to normalize inputs+	  to not unnecessarily normalize them.++	* Speedup 'normalize' and 'toDecimalDigits'. Now both are practically+	  linear in the coefficient size.+	  Thanks to Andrzej Rybczak for reporting these issues.+ 0.3.7.0  	* Make division (/) on Scientifics slightly more efficient.
scientific.cabal view
@@ -1,5 +1,5 @@ name:               scientific-version:            0.3.8.1+version:            0.3.9.0 synopsis:           Numbers represented using scientific notation description:   "Data.Scientific" provides the number type 'Scientific'. Scientific numbers are@@ -64,9 +64,11 @@   default:     False  library+  -- main module first, so it's loaded into cabal repl.   exposed-modules:-    Data.ByteString.Builder.Scientific     Data.Scientific+  exposed-modules:+    Data.ByteString.Builder.Scientific     Data.Text.Lazy.Builder.Scientific    other-modules:@@ -127,11 +129,9 @@     , bytestring     , QuickCheck        >=2.14.2     , scientific-    , smallcheck        >=1.0     , tasty             >=1.4.0.1     , tasty-hunit       >=0.8     , tasty-quickcheck  >=0.8-    , tasty-smallcheck  >=0.2     , text  benchmark bench-scientific
src/Data/Scientific.hs view
@@ -6,6 +6,7 @@ {-# LANGUAGE Trustworthy #-} {-# LANGUAGE DeriveLift #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE ViewPatterns #-}  -- | -- Module      :  Data.Scientific@@ -93,7 +94,6 @@     , normalize     ) where - ---------------------------------------------------------------------- -- Imports ----------------------------------------------------------------------@@ -107,6 +107,7 @@ import           Data.Hashable                (Hashable(..)) import           Data.Int                     (Int8, Int16, Int32, Int64) import qualified Data.Map            as M     (Map, empty, insert, lookup)+import           Data.Maybe                   (isJust) import           Data.Ratio                   ((%), numerator, denominator) import           Data.Typeable                (Typeable) import           Data.Word                    (Word8, Word16, Word32, Word64)@@ -207,38 +208,63 @@ -- is calculated so there's no risk of a blowup in space or time when comparing -- scientific numbers coming from untrusted sources. instance Eq Scientific where-    s1 == s2 = c1 == c2 && e1 == e2-      where-        Scientific c1 e1 = normalize s1-        Scientific c2 e2 = normalize s2+    Scientific c1 e1 == Scientific c2 e2+        -- if exponents are equal we can compare the coefficients+        | e1 == e2 = c1 == c2 +        -- if numbers are normalised (i.e. no trailing zeroes in coefficient)+        -- we can also compare them directly+        | rem c1 10 /= 0+        , rem c2 10 /= 0+        = e1 == e2 && c1 == c2++    Scientific c1 e1 == Scientific c2 e2 = case compare c1 0 of+        EQ -> c2 == 0+        LT -> if c2 < 0 then eqScientific1 (-c1) e1 (-c2) e2 else False+        GT -> if c2 > 0 then eqScientific1   c1  e1   c2  e2 else False++-- | Equality comparison of positive scientific numbers.+-- The coefficients c1 and c2 are positive.+eqScientific1 :: Integer -> Int -> Integer -> Int -> Bool+eqScientific1 c1 e1 c2 e2+    | log1 /= log2 = False  -- if logarithms are non-equal, numbers cannot be equal+    | otherwise = case compare e1 e2 of+        EQ -> c1 == c2+        -- an alternative is to divide by the difference,+        -- and check that remainder is zero.+        --+        -- I think it doesn't matter in practice.+        GT -> c1 * magnitude (e1 - e2) == c2+        LT -> c1                       == c2 * magnitude (e2 - e1)+  where+    log1 = integerLog10' c1 + e1+    log2 = integerLog10' c2 + e2+ -- | Scientific numbers can be safely compared for ordering. No magnitude @10^e@ -- is calculated so there's no risk of a blowup in space or time when comparing -- scientific numbers coming from untrusted sources. instance Ord Scientific where-    compare s1 s2-        | c1 == c2 && e1 == e2 = EQ-        | c1 < 0    = if c2 < 0 then cmp (-c2) e2 (-c1) e1 else LT-        | c1 > 0    = if c2 > 0 then cmp   c1  e1   c2  e2 else GT-        | otherwise = if c2 > 0 then LT else GT-      where-        Scientific c1 e1 = normalize s1-        Scientific c2 e2 = normalize s2--        cmp cx ex cy ey-            | log10sx < log10sy = LT-            | log10sx > log10sy = GT-            | d < 0     = if cx <= (cy `quotInteger` magnitude (-d)) then LT else GT-            | d > 0     = if cy >  (cx `quotInteger` magnitude   d)  then LT else GT-            | otherwise = if cx < cy                                 then LT else GT-          where-            log10sx = log10cx + ex-            log10sy = log10cy + ey+    compare (Scientific c1 e1) (Scientific c2 e2)+        | e1 == e2 = compare c1 c2 -            log10cx = integerLog10' cx-            log10cy = integerLog10' cy+    compare (Scientific c1 e1) (Scientific c2 e2) = case compare c1 0 of+        EQ -> compare 0 c2+        LT -> if c2 < 0 then cmpScientific (-c2) e2 (-c1) e1 else LT+        GT -> if c2 > 0 then cmpScientific   c1  e1   c2  e2 else GT -            d = log10cx - log10cy+-- | Order comparison of positive scientific numbers.+-- The coeffients c1 and c2 are positive.+cmpScientific :: Integer -> Int -> Integer -> Int -> Ordering+cmpScientific c1 e1 c2 e2 = case compare log1 log2 of+    GT -> GT+    LT -> LT+    EQ -> case compare e1 e2 of+        EQ -> compare c1 c2+        GT -> compare (c1 * magnitude (e1 - e2)) c2+        LT -> compare c1 (c2 * magnitude (e2 - e1))+  where+    log1 = integerLog10' c1 + e1+    log2 = integerLog10' c2 + e2  -- | /WARNING:/ '+' and '-' compute the 'Integer' magnitude: @10^e@ where @e@ is -- the difference between the @'base10Exponent's@ of the arguments. If these@@ -665,11 +691,6 @@ toIntegral (Scientific c e) = fromInteger c * magnitude e {-# INLINE toIntegral #-} ----- ---------------------------------------------------------------------- -- Conversions ----------------------------------------------------------------------@@ -769,24 +790,16 @@ -- This function also guards against computing huge Integer magnitudes (@10^e@) -- that could fill up all space and crash your program. toBoundedInteger :: forall i. (Integral i, Bounded i) => Scientific -> Maybe i-toBoundedInteger s-    | c == 0    = fromIntegerBounded 0-    | integral  = if dangerouslyBig-                  then Nothing-                  else fromIntegerBounded n-    | otherwise = Nothing+toBoundedInteger (isInteger_ -> Just (Scientific c e))+    | c == 0         = fromIntegerBounded 0+    | e == 0         = fromIntegerBounded c+    | dangerouslyBig = Nothing+    | otherwise      = fromIntegerBounded n   where-    c = coefficient s--    integral = e >= 0 || e' >= 0--    e  = base10Exponent s-    e' = base10Exponent s'--    s' = normalize s+    l  = integerLog10' (abs c) + e -    dangerouslyBig = e > limit &&-                     e > integerLog10' (max (abs iMinBound) (abs iMaxBound))+    -- whether logarithm of s is bigger than logarithm of source type bounds+    dangerouslyBig = l > 1 + integerLog10' (max (abs iMinBound) (abs iMaxBound))      fromIntegerBounded :: Integer -> Maybe i     fromIntegerBounded i@@ -797,10 +810,12 @@     iMaxBound = toInteger (maxBound :: i)      -- This should not be evaluated if the given Scientific is dangerouslyBig-    -- since it could consume all space and crash the process:+    -- since it could consume all space and crash the process     n :: Integer-    n = toIntegral s'+    n = c * magnitude e +toBoundedInteger _ = Nothing+ {-# SPECIALIZE toBoundedInteger :: Scientific -> Maybe Int #-} {-# SPECIALIZE toBoundedInteger :: Scientific -> Maybe Int8 #-} {-# SPECIALIZE toBoundedInteger :: Scientific -> Maybe Int16 #-}@@ -830,12 +845,11 @@ -- Also see: 'isFloating' or 'isInteger'. floatingOrInteger :: (RealFloat r, Integral i) => Scientific -> Either r i floatingOrInteger s-    | base10Exponent s  >= 0 = Right (toIntegral   s)-    | base10Exponent s' >= 0 = Right (toIntegral   s')-    | otherwise              = Left  (toRealFloat  s')-  where-    s' = normalize s+    | Just s' <- isInteger_ s+    = Right (toIntegral s') +    | otherwise+    = Left (toRealFloat s)  ---------------------------------------------------------------------- -- Predicates@@ -851,12 +865,31 @@ -- -- Also see: 'floatingOrInteger'. isInteger :: Scientific -> Bool-isInteger s = base10Exponent s  >= 0 ||-              base10Exponent s' >= 0-  where-    s' = normalize s+isInteger = isJust . isInteger_ +-- | Like 'isInteger', but if number is integer, return+-- 'Scientific' such that 'base10exponent' is non-negative.+-- /Note:/ this resulting scientific number might still be not 'normalise'd.+--+-- @since 0.3.9+--+isInteger_ :: Scientific -> Maybe Scientific+isInteger_ s@(Scientific c e)+    | e >= 0 = Just s+    | c == 0 = Just (Scientific c 0)+    | integerLog10' (abs c) < negate e = Nothing +    -- here the magnitude (negate e) is smaller than c because of previous check.+    -- thus dividing by it once is at least as fast as normalising of whole scientific number+    -- in the worst case.+    | c < 0+    , let (q, r) = quotRem (negate c) (magnitude (negate e))+    = if r == 0 then Just (Scientific (negate q) 0) else Nothing++    | otherwise+    , let (q, r) = quotRem c (magnitude (negate e))+    = if r == 0 then Just (Scientific q 0) else Nothing+ ---------------------------------------------------------------------- -- Parsing ----------------------------------------------------------------------@@ -898,7 +931,8 @@       step a digit = a * 10 + fromIntegral digit       {-# INLINE step #-} -  n <- foldDigits step 0+  ds <- ReadP.munch1 isDecimal+  let n = read ds :: Integer    let s = SP n 0       fractional = foldDigits (\(SP a e) digit ->@@ -1079,15 +1113,10 @@ toDecimalDigits (Scientific 0  _)  = ([0], 0) toDecimalDigits (Scientific c' e') =     case normalizePositive c' e' of-      Scientific c e -> go c 0 []+      Scientific c e -> (ds, length ds + e)         where-          go :: Integer -> Int -> [Int] -> ([Int], Int)-          go 0 !n ds = (ds, ne) where !ne = n + e-          go i !n ds = case i `quotRemInteger` 10 of-                         (# q, r #) -> go q (n+1) (d:ds)-                           where-                             !d = fromIntegral r-+          -- show for Integer is faster than repeated quotRem _ 10+          ds = map (\d -> ord d - ord '0') (show c)  ---------------------------------------------------------------------- -- Normalization@@ -1099,13 +1128,23 @@ -- You should rarely have a need for this function since scientific numbers are -- automatically normalized when pretty-printed and in 'toDecimalDigits'. normalize :: Scientific -> Scientific-normalize (Scientific c e)-    | c > 0 =   normalizePositive   c  e-    | c < 0 = -(normalizePositive (-c) e)-    | otherwise {- c == 0 -} = Scientific 0 0+normalize (Scientific c e) = case compare c 0 of+    GT ->   normalizePositive   c  e+    LT -> -(normalizePositive (-c) e)+    EQ -> Scientific 0 0  normalizePositive :: Integer -> Int -> Scientific-normalizePositive !c !e = case quotRemInteger c 10 of-                            (# c', r #)-                                | r == 0    -> normalizePositive c' (e+1)-                                | otherwise -> Scientific c e+normalizePositive !c !e = case stripPowers c 10 of+    (c', k) -> Scientific c' (e+k)++stripPowers :: Integer -> Integer -> (Integer, Int)+stripPowers !c !p+    | r /= 0+    = (c, 0)++    -- remove factors of p*p; this speedups the normalisation by quite a bit.+    | let (c', k) = stripPowers q (p*p)+    , let (q', r') = quotRem c' p+    = if r' == 0 then (q', 2 * k + 2) else (c', 2 * k + 1)+  where+    (q, r) = quotRem c p
test/test.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeApplications #-}  {-# OPTIONS_GHC -fno-warn-orphans #-} @@ -14,12 +15,10 @@ import           Data.Word import           Data.Scientific                    as Scientific import           Test.Tasty-import           Test.Tasty.HUnit                          (testCase, (@?=), Assertion, assertBool)-import qualified Test.SmallCheck                    as SC-import qualified Test.SmallCheck.Series             as SC-import qualified Test.Tasty.SmallCheck              as SC  (testProperty)+import           Test.Tasty.HUnit                          (testCase, (@?=), (@=?), Assertion, assertBool)+import           Test.QuickCheck                       (Property, (===), (.&&.)) import qualified Test.QuickCheck                    as QC-import qualified Test.Tasty.QuickCheck              as QC  (testProperty)+import           Test.Tasty.QuickCheck                 (testProperty) import qualified Data.Binary                        as Binary (encode, decode) import qualified Data.Text.Lazy                     as TL  (unpack) import qualified Data.Text.Lazy.Builder             as TLB (toLazyText)@@ -35,14 +34,58 @@ main = testMain $ testGroup "scientific"   [ testGroup "DoS protection"     [ testGroup "Eq"-      [ testCase "1e1000000" $ assertBool "" $-          (read "1e1000000" :: Scientific) == (read "1e1000000" :: Scientific)+      [ testCase "1e1000000" $ assertBool "" $ (read "1e1000000" :: Scientific) == (read "1e1000000" :: Scientific)+      , testCase "1e1000000 ineq" $ assertBool "" $ (read "1e1000000" :: Scientific) /= (read "1e1000002" :: Scientific)++      -- this also indirectly checks that 'read' is fast enough.+      , testCase "10...0" $ assertBool "" $+          (read "1e1000000" :: Scientific) ==+          (read ('1' : replicate 1000000 '0'))       ]     , testGroup "Ord"       [ testCase "compare 1234e1000000 123e1000001" $           compare (read "1234e1000000" :: Scientific) (read "123e1000001" :: Scientific) @?= GT++      , testCase "10...0" $+          compare (read "1e1000001" :: Scientific)+                  (read ('1' : replicate 1000000 '0' ++ "0"))+              @?= EQ+      , testCase "1...1" $+          compare (read "1e1000001" :: Scientific)+                  (read ('1' : replicate 1000000 '0' ++ "1"))+              @?= LT       ] +    , testGroup "isInteger"+        [ testCase "1e1000000" $ True @=? isInteger (read "1e1000000" :: Scientific)+        , testCase "10...0e-1" $ True @=? isInteger (read $ '1' : replicate 1000000 '0' ++ "e-1" :: Scientific)+        , testCase "10...0e-10...0" $ True @=? isInteger (read $ '1' : replicate 1000000 '0' ++ "e-1000000" :: Scientific)+        , testCase "10...0e-20...0" $ False @=? isInteger (read $ '1' : replicate 1000000 '0' ++ "e-2000000" :: Scientific)+        ]++    , testGroup "toBoundedInteger"+        [ testCase "1e1000000" $ Nothing @=? toBoundedInteger @Int (read "1e1000000") +        , testCase "10...0e-1" $ Nothing @=? toBoundedInteger @Int (read $ '1' : replicate 1000000 '0' ++ "e-1")+        ]++    , testGroup "floatingOrInteger"+        [ testCase "1e1000000" $ Right (10 ^ (1000000 :: Int) :: Integer) @=? floatingOrInteger @Double @Integer (read "1e1000000") +        , testCase "10...0e-1" $ Right (10 ^ ( 999999 :: Int) :: Integer) @=? floatingOrInteger @Double @Integer (read $ '1' : replicate 1000000 '0' ++ "e-1")+        ]++    , testGroup "normalize"+        [ testCase "1e1000000" $ True @=? isInteger (normalize (read "1e1000000" :: Scientific))+        , testCase "10...0e-1" $ True @=? isInteger (normalize (read $ '1' : replicate 1000000 '0' ++ "e-1" :: Scientific))+        , testCase "10...0e-10...0" $ True @=? isInteger (normalize (read $ '1' : replicate 1000000 '0' ++ "e-1000000" :: Scientific))+        , testCase "10...0e-20...0" $ False @=? isInteger (normalize (read $ '1' : replicate 1000000 '0' ++ "e-2000000" :: Scientific))+        ]++    , testGroup "toDecimalDigits"+        [ testCase "9...9" $ do+            let (ds, n) = toDecimalDigits (read $ replicate 1000000 '9')+            (1000000,1000000) @=? (length ds, n)+        ]+     , testGroup "RealFrac"       [ testGroup "floor"         [ testCase "1e1000000"   $ (floor (read "1e1000000"   :: Scientific) :: Int) @?= 0@@ -77,20 +120,15 @@                                   (toRealFloat (read "1e1000000" :: Scientific) :: Double)       , testCase "1e-1000000" $ (toRealFloat (read "1e-1000000" :: Scientific) :: Double) @?= 0       ]-    , testGroup "toBoundedInteger"-      [ testCase "1e1000000"  $ (toBoundedInteger (read "1e1000000" :: Scientific) :: Maybe Int) @?= Nothing-      ]     ] -  , smallQuick "normalization"-       (SC.over   normalizedScientificSeries $ \s ->-            s /= 0 SC.==> abs (Scientific.coefficient s) `mod` 10 /= 0)+  , testProperty "normalization"        (QC.forAll normalizedScientificGen    $ \s ->             s /= 0 QC.==> abs (Scientific.coefficient s) `mod` 10 /= 0)    , testGroup "Binary"     [ testProperty "decode . encode == id" $ \s ->-        Binary.decode (Binary.encode s) === s+        Binary.decode (Binary.encode s) === theSci s     ]    , testGroup "Parsing"@@ -108,18 +146,16 @@     ]    , testGroup "Formatting"-    [ testProperty "read . show == id" $ \s -> read (show s) === s+    [ testProperty "read . show == id" $ \s -> read (show s) === theSci s     , testCase "show (Just 1)"    $ testShow (Just 1)    "Just 1.0"     , testCase "show (Just 0)"    $ testShow (Just 0)    "Just 0.0"     , testCase "show (Just (-1))" $ testShow (Just (-1)) "Just (-1.0)"      , testGroup "toDecimalDigits"-      [ smallQuick "laws"-          (SC.over   nonNegativeScientificSeries toDecimalDigits_laws)+      [ testProperty "laws"           (QC.forAll nonNegativeScientificGen    toDecimalDigits_laws) -      , smallQuick "== Numeric.floatToDigits"-          (toDecimalDigits_eq_floatToDigits . SC.getNonNegative)+      , testProperty "== Numeric.floatToDigits"           (toDecimalDigits_eq_floatToDigits . QC.getNonNegative)       ] @@ -157,7 +193,7 @@    , testGroup "Num"     [ testGroup "Equal to Rational"-      [ testProperty "fromInteger" $ \i -> fromInteger i === fromRational (fromInteger i)+      [ testProperty "fromInteger" $ \i -> fromInteger i === theSci (fromRational (fromInteger i))       , testProperty "+"           $ bin (+)       , testProperty "-"           $ bin (-)       , testProperty "*"           $ bin (*)@@ -166,27 +202,26 @@       , testProperty "signum"      $ unary signum       ] -    , testProperty "0 identity of +" $ \a -> a + 0 === a-    , testProperty "1 identity of *" $ \a -> 1 * a === a-    , testProperty "0 identity of *" $ \a -> 0 * a === 0+    , testProperty "0 identity of +" $ \a -> a + 0 === theSci a+    , testProperty "1 identity of *" $ \a -> 1 * a === theSci a+    , testProperty "0 identity of *" $ \a -> 0 * a === theSci 0 -    , testProperty "associativity of +"         $ \a b c -> a + (b + c) === (a + b) + c-    , testProperty "commutativity of +"         $ \a b   -> a + b       === b + a-    , testProperty "distributivity of * over +" $ \a b c -> a * (b + c) === a * b + a * c+    , testProperty "associativity of +"         $ \a b c -> a + (b + c) === (a + b) + theSci c+    , testProperty "commutativity of +"         $ \a b   -> a + b       === b + theSci a+    , testProperty "distributivity of * over +" $ \a b c -> a * (b + c) === a * b + a * theSci c -    , testProperty "subtracting the addition" $ \x y -> x + y - y === x+    , testProperty "subtracting the addition" $ \x y -> x + y - y === theSci x -    , testProperty "+ and negate" $ \x -> x + negate x === 0-    , testProperty "- and negate" $ \x -> x - negate x === x + x+    , testProperty "+ and negate" $ \x -> theSci x + negate x === 0+    , testProperty "- and negate" $ \x -> theSci x - negate x === x + x -    , smallQuick "abs . negate == id"-        (SC.over   nonNegativeScientificSeries $ \x -> abs (negate x) === x)-        (QC.forAll nonNegativeScientificGen    $ \x -> abs (negate x) === x)+    , testProperty "abs . negate == id"+        (QC.forAll nonNegativeScientificGen    $ \x -> abs (negate x) === theSci x)     ]    , testGroup "Real"     [ testProperty "fromRational . toRational == id" $ \x ->-        (fromRational . toRational) x === x+        (fromRational . toRational) x === theSci x     ]    , testGroup "RealFrac"@@ -194,7 +229,7 @@       [ testProperty "properFraction" $ \x ->           let (n1::Integer, f1::Scientific) = properFraction x               (n2::Integer, f2::Rational)   = properFraction (toRational x)-          in (n1 == n2) && (f1 == fromRational f2)+          in (n1 === n2) .&&. (f1 === fromRational f2)        , testProperty "round" $ \(x::Scientific) ->           (round x :: Integer) == round (toRational x)@@ -238,15 +273,15 @@                     s' = normalize s       , testProperty "Integer == Right" $ \(i::Integer) ->           (floatingOrInteger (fromInteger i) :: Either Double Integer) == Right i-      , smallQuick "Double == Left"-          (\(d::Double) -> genericIsFloating d SC.==>-             (floatingOrInteger (realToFrac d) :: Either Double Integer) == Left d)+      , testProperty "Double == Left"           (\(d::Double) -> genericIsFloating d QC.==>              (floatingOrInteger (realToFrac d) :: Either Double Integer) == Left d)       ]     , testGroup "toBoundedInteger"       [ testGroup "correct conversion"-        [ testProperty "Int64"       $ toBoundedIntegerConversion (undefined :: Int64)+      +        [ testCase "100e-2" $ toBoundedInteger @Int (read "100e-2") @?= Just 1+        , testProperty "Int64"       $ toBoundedIntegerConversion (undefined :: Int64)         , testProperty "Word64"      $ toBoundedIntegerConversion (undefined :: Word64)         , testProperty "NegativeNum" $ toBoundedIntegerConversion (undefined :: NegativeInt)         ]@@ -276,6 +311,10 @@     ]   ] +-- used as type annotation+theSci :: Scientific -> Scientific+theSci = id+ testMain :: TestTree -> IO () testMain = defaultMainWithIngredients defaultIngredients @@ -302,7 +341,6 @@ conversionsProperties :: forall realFloat.                          ( RealFloat    realFloat                          , QC.Arbitrary realFloat-                         , SC.Serial IO realFloat                          , Show         realFloat                          )                       => realFloat -> [TestTree]@@ -338,23 +376,6 @@                  s < fromIntegral (minBound :: i) ||                  s > fromIntegral (maxBound :: i) -testProperty :: (SC.Testable IO test, QC.Testable test)-             => TestName -> test -> TestTree-testProperty n test = smallQuick n test test--smallQuick :: (SC.Testable IO smallCheck, QC.Testable quickCheck)-             => TestName -> smallCheck -> quickCheck -> TestTree-smallQuick n sc qc = testGroup n-                     [ SC.testProperty "smallcheck" sc-                     , QC.testProperty "quickcheck" qc-                     ]---- | ('==') specialized to 'Scientific' so we don't have to put type--- signatures everywhere.-(===) :: Scientific -> Scientific -> Bool-(===) = (==)-infix 4 ===- bin :: (forall a. Num a => a -> a -> a) -> Scientific -> Scientific -> Bool bin op a b = toRational (a `op` b) == toRational a `op` toRational b @@ -378,10 +399,10 @@    in rule1 && rule2 && rule3 && rule4 -properFraction_laws :: Scientific -> Bool-properFraction_laws x = fromInteger n + f === x        &&-                        (positive n == posX || n == 0) &&-                        (positive f == posX || f == 0) &&+properFraction_laws :: Scientific -> Property+properFraction_laws x = fromInteger n + f === x        .&&.+                        (positive n == posX || n == 0) .&&.+                        (positive f == posX || f == 0) .&&.                         abs f < 1     where       posX = positive x@@ -419,23 +440,6 @@     maxBound = -10  ------------------------------------------------------------------------- SmallCheck instances-------------------------------------------------------------------------instance (Monad m) => SC.Serial m Scientific where-    series = scientifics--scientifics :: (Monad m) => SC.Series m Scientific-scientifics = SC.cons2 scientific--nonNegativeScientificSeries :: (Monad m) => SC.Series m Scientific-nonNegativeScientificSeries = liftM SC.getNonNegative SC.series--normalizedScientificSeries :: (Monad m) => SC.Series m Scientific-normalizedScientificSeries = liftM Scientific.normalize SC.series------------------------------------------------------------------------- -- QuickCheck instances ---------------------------------------------------------------------- @@ -447,10 +451,13 @@                         <*> bigIntGen)       , (10, scientific <$> pure 0                         <*> bigIntGen)+      , (10, (\c e' e -> scientific (c * 10 ^ min 10 (abs e')) e) <$> QC.arbitrary <*> intGen <*> intGen)       ] -    shrink s = zipWith scientific (QC.shrink $ Scientific.coefficient s)-                                  (QC.shrink $ Scientific.base10Exponent s)+    shrink s = +        [ scientific c e+        | (c, e) <- QC.shrink (Scientific.coefficient s, Scientific.base10Exponent s)+        ]  nonNegativeScientificGen :: QC.Gen Scientific nonNegativeScientificGen =