packages feed

scientific 0.3.2.2 → 0.3.3.0

raw patch · 4 files changed

+139/−32 lines, 4 filesdep +tasty-hunit

Dependencies added: tasty-hunit

Files

changelog view
@@ -1,6 +1,13 @@+0.3.3.0+	* Add the isFloating or isInteger predicates.+	  Courtesy of Zejun Wu (@watashi).+	* Add the toRealFloat' and toBoundedInteger functions.+	  Courtesy of Fujimura Daisuke (@fujimura).+ 0.3.2.2 	* Enable package to link with integer-simple instead of 	  integer-gmp using the -finteger-simple cabal flag.+	  Courtesy of @k0ral.  0.3.2.1 
scientific.cabal view
@@ -1,5 +1,5 @@ name:                scientific-version:             0.3.2.2+version:             0.3.3.0 synopsis:            Numbers represented using scientific notation description:   @Data.Scientific@ provides a space efficient and arbitrary precision@@ -95,6 +95,7 @@                , base             >= 4.3   && < 4.8                , tasty            >= 0.5   && < 0.9                , tasty-ant-xml    >= 1.0   && < 1.1+               , tasty-hunit      >= 0.8   && < 0.9                , tasty-smallcheck >= 0.2   && < 0.9                , tasty-quickcheck >= 0.8   && < 0.9                , smallcheck       >= 1.0   && < 1.2
src/Data/Scientific.hs view
@@ -38,6 +38,12 @@ -- to @'Integral's@ (like: 'Int') or @'RealFloat's@ (like: 'Double' or 'Float') -- will always be bounded by the target type. --+-- /WARNING:/ Although @Scientific@ is an instance of 'Fractional', the methods+-- are only partially defined! Specifically 'recip' and '/' will diverge+-- (i.e. loop and consume all space) when their outputs have an infinite decimal+-- expansion. 'fromRational' will diverge when the input 'Rational' has an+-- infinite decimal expansion.+-- -- This module is designed to be imported qualified: -- -- @import Data.Scientific as Scientific@@@ -51,10 +57,16 @@     , coefficient     , base10Exponent +      -- * Predicates+    , isFloating+    , isInteger+       -- * Conversions-    , fromFloatDigits-    , toRealFloat     , floatingOrInteger+    , toRealFloat+    , toBoundedRealFloat+    , toBoundedInteger+    , fromFloatDigits        -- * Pretty printing     , formatScientific@@ -216,9 +228,9 @@   "realToFrac_toRealFloat_Float"    realToFrac = toRealFloat :: Scientific -> Float #-} --- | /WARNING:/ 'recip' and '/' will diverge when their outputs have--- an infinite decimal expansion. 'fromRational' will diverge when the--- input 'Rational' has an infinite decimal expansion.+-- | /WARNING:/ 'recip' and '/' will diverge (i.e. loop and consume all space)+-- when their outputs have an infinite decimal expansion. 'fromRational' will+-- diverge when the input 'Rational' has an infinite decimal expansion. instance Fractional Scientific where     recip = fromRational . recip . toRational     {-# INLINE recip #-}@@ -326,7 +338,7 @@ -- space usage is bounded by the target type. -- -- For large negative exponents we check if the exponent is smaller--- than some limit (currently -1100). In that case we know that the+-- than some limit (currently -324). In that case we know that the -- scientific number is really small (unless the coefficient has many -- digits) so we can immediately return -1 for negative scientific -- numbers or 0 for positive numbers.@@ -422,21 +434,29 @@           go []     !c !n = Scientific c (e - n)           go (d:ds) !c !n = go ds (c * 10 + fromIntegral d) (n + 1) --- | Convert a 'Scientific' number into a 'RealFloat' (like a 'Double'--- or a 'Float').+-- | Safely convert a 'Scientific' number into a 'RealFloat' (like a 'Double' or a+-- 'Float'). ----- Note that this function uses 'realToFrac'--- (@'fromRational' . 'toRational'@) internally but it guards against--- computing huge Integer magnitudes (@10^e@) that could fill up all--- space and crash your program.+-- Note that this function uses 'realToFrac' (@'fromRational' . 'toRational'@)+-- internally but it guards against computing huge Integer magnitudes (@10^e@)+-- that could fill up all space and crash your program. If the 'base10Exponent'+-- of the given 'Scientific' is too big or too small to be represented in the+-- target type, Infinity or 0 will be returned respectively. Use+-- 'toBoundedRealFloat' which explicitly handles this case by returning 'Left'. ----- Always prefer 'toRealFloat' over 'realToFrac' when converting from--- scientific numbers coming from an untrusted source.-toRealFloat :: forall a. (RealFloat a) => Scientific -> a-toRealFloat s@(Scientific c e)-    | e >  limit && e > hiLimit                    = sign (1/0) -- Infinity-    | e < -limit && e < loLimit && e + d < loLimit = sign 0-    | otherwise                                    = realToFrac s+-- Always prefer 'toRealFloat' over 'realToFrac' when converting from scientific+-- numbers coming from an untrusted source.+toRealFloat :: (RealFloat a) => Scientific -> a+toRealFloat = either id id . toBoundedRealFloat++-- | Preciser version of `toRealFloat`. If the 'base10Exponent' of the given+-- 'Scientific' is too big or too small to be represented in the target type,+-- Infinity or 0 will be returned as 'Left'.+toBoundedRealFloat :: forall a. (RealFloat a) => Scientific -> Either a a+toBoundedRealFloat s@(Scientific c e)+    | e >  limit && e > hiLimit                    = Left  $ sign (1/0) -- Infinity+    | e < -limit && e < loLimit && e + d < loLimit = Left  $ sign 0+    | otherwise                                    = Right $ realToFrac s   where     (loLimit, hiLimit) = exponentLimits (undefined :: a) @@ -459,9 +479,44 @@       digits   = floatDigits (undefined :: a)       (lo, hi) = floatRange  (undefined :: a) +-- | Convert a `Scientific` to a bounded integer.+--+-- If the given `Scientific` doesn't fit in the target representation, it will+-- return `Nothing`.+--+-- 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+    | integral  = if dangerouslyBig+                  then Nothing+                  else if n < toInteger (minBound :: i) ||+                          n > toInteger (maxBound :: i)+                       then Nothing+                       else Just $ fromInteger n+    | otherwise = Nothing+  where+    integral = e >= 0 || e' >= 0++    e  = base10Exponent s+    e' = base10Exponent s'++    s' = normalize s++    dangerouslyBig = e > limit &&+                     e > integerLog10' (max (abs $ toInteger (minBound :: i))+                                            (abs $ toInteger (maxBound :: i)))++    -- This should not be evaluated if the given Scientific is dangerouslyBig+    -- since it could consume all space and crash the process:+    n :: Integer+    n = toIntegral s'+ -- | @floatingOrInteger@ determines if the scientific is floating point -- or integer. In case it's floating-point the scientific is converted -- to the desired 'RealFloat' using 'toRealFloat'.+--+-- Also see: 'isFloating' or 'isInteger'. floatingOrInteger :: (RealFloat r, Integral i) => Scientific -> Either r i floatingOrInteger s     | base10Exponent s  >= 0 = Right (toIntegral   s)@@ -472,6 +527,26 @@   ----------------------------------------------------------------------+-- Predicates+----------------------------------------------------------------------++-- | Return 'True' if the scientific is a floating point, 'False' otherwise.+--+-- Also see: 'floatingOrInteger'.+isFloating :: Scientific -> Bool+isFloating = not . isInteger++-- | Return 'True' if the scientific is an integer, 'False' otherwise.+--+-- Also see: 'floatingOrInteger'.+isInteger :: Scientific -> Bool+isInteger s = base10Exponent s  >= 0 ||+              base10Exponent s' >= 0+  where+    s' = normalize s+++---------------------------------------------------------------------- -- Parsing ---------------------------------------------------------------------- @@ -640,13 +715,10 @@ -- -- then -----      (1) @n >= 1@------      (2) @x = 0.d1d2...dn * (10^^e)@------      (3) @0 <= di <= 9@------      (4) @null $ takeWhile (==0) $ reverse [d1,d2,...,dn]@+--     1. @n >= 1@+--     2. @x = 0.d1d2...dn * (10^^e)@+--     3. @0 <= di <= 9@+--     4. @null $ takeWhile (==0) $ reverse [d1,d2,...,dn]@ -- -- The last property means that the coefficient will be normalized, i.e. doesn't -- contain trailing zeros.
test/test.hs view
@@ -11,9 +11,11 @@  import           Control.Applicative import           Control.Monad+import           Data.Int import           Data.Scientific                    as Scientific import           Test.Tasty import           Test.Tasty.Runners.AntXML+import           Test.Tasty.HUnit import qualified Test.SmallCheck                    as SC import qualified Test.SmallCheck.Series             as SC import qualified Test.Tasty.SmallCheck              as SC  (testProperty)@@ -142,20 +144,45 @@       , testProperty "Integer == Right" $ \(i::Integer) ->           (floatingOrInteger (fromInteger i) :: Either Double Integer) == Right i       , smallQuick "Double == Left"-          (\(d::Double) -> isFloating d SC.==>+          (\(d::Double) -> genericIsFloating d SC.==>              (floatingOrInteger (realToFrac d) :: Either Double Integer) == Left d)-          (\(d::Double) -> isFloating d QC.==>+          (\(d::Double) -> genericIsFloating d QC.==>              (floatingOrInteger (realToFrac d) :: Either Double Integer) == Left d)       ]+    , testGroup "toBoundedInteger"+      [ testProperty "correct conversion" $ \s ->+            case toBoundedInteger s :: Maybe Int64 of+              Just i -> i == (fromIntegral $ (coefficient s') * 10^(base10Exponent s'))+                where+                  s' = normalize s+              Nothing -> isFloating s ||+                         s < fromIntegral (minBound :: Int64) ||+                         s > fromIntegral (maxBound :: Int64)+      ]     ]+  , testGroup "toBoundedInteger"+    [ testGroup "to Int64" $+      [ testCase "succ of maxBound" $+        let i = succ . fromIntegral $ (maxBound :: Int64)+            s = scientific i 0+        in (toBoundedInteger s :: Maybe Int64) @?= Nothing+      , testCase "pred of minBound" $+        let i = pred . fromIntegral $ (minBound :: Int64)+            s = scientific i 0+        in (toBoundedInteger s :: Maybe Int64) @?= Nothing+      ]+    ]+  , testGroup "Predicates"+    [ testProperty "isFloating" $ \s -> isFloating s ==      genericIsFloating s+    , testProperty "isInteger"  $ \s -> isInteger  s == not (genericIsFloating s)+    ]   ]  testMain :: TestTree -> IO () testMain = defaultMainWithIngredients (antXMLRunner:defaultIngredients) -isFloating :: Double -> Bool-isFloating 0 = False-isFloating d = fromInteger (floor d :: Integer) /= d+genericIsFloating :: RealFrac a => a -> Bool+genericIsFloating a = fromInteger (floor a :: Integer) /= a  conversionsProperties :: forall realFloat.                          ( RealFloat    realFloat