Hello community, here is the log from the commit of package ghc-scientific for openSUSE:Factory checked in at 2018-05-30 12:13:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ghc-scientific (Old) and /work/SRC/openSUSE:Factory/.ghc-scientific.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-scientific" Wed May 30 12:13:00 2018 rev:18 rq:607878 version:0.3.6.2 Changes: -------- --- /work/SRC/openSUSE:Factory/ghc-scientific/ghc-scientific.changes 2017-07-27 11:12:10.989988799 +0200 +++ /work/SRC/openSUSE:Factory/.ghc-scientific.new/ghc-scientific.changes 2018-05-30 12:27:07.338660845 +0200 @@ -1,0 +2,51 @@ +Mon May 14 17:02:11 UTC 2018 - [email protected] + +- Update scientific to version 0.3.6.2. + * Due to a regression introduced in 0.3.4.14 the RealFrac methods + and floatingOrInteger became vulnerable to a space blowup when + applied to scientifics with huge exponents. This has now been + fixed again. + + * Fix build on GHC < 8. + + * Make the methods of the Hashable, Eq and Ord instances safe to + use when applied to scientific numbers coming from untrusted + sources. Previously these methods first converted their arguments + to Rational before applying the operation. This is unsafe because + converting a Scientific to a Rational could fill up all space and + crash your program when the Scientific has a huge base10Exponent. + + Do note that the hash computation of the Hashable Scientific + instance has been changed because of this improvement! + + Thanks to Tom Sydney Kerckhove (@NorfairKing) for pushing me to + fix this. + + * fromRational :: Rational -> Scientific now throws an error + instead of diverging when applied to a repeating decimal. This + does mean it will consume space linear in the number of digits of + the resulting scientific. This makes "fromRational" and the other + Fractional methods "recip" and "/" a bit safer to use. + + * To get the old unsafe but more efficient behaviour the following + function was added: unsafeFromRational :: Rational -> Scientific. + + * Add alternatives for fromRationalRepetend: + + fromRationalRepetendLimited + :: Int -- ^ limit + -> Rational + -> Either (Scientific, Rational) + (Scientific, Maybe Int) + + and: + + fromRationalRepetendUnlimited + :: Rational -> (Scientific, Maybe Int) + + Thanks to Ian Jeffries (@seagreen) for the idea. + + * Dropped upper version bounds of dependencies + because it's to much work to maintain. + +------------------------------------------------------------------- Old: ---- scientific-0.3.5.1.tar.gz New: ---- scientific-0.3.6.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ghc-scientific.spec ++++++ --- /var/tmp/diff_new_pack.22Ehxm/_old 2018-05-30 12:27:08.882607507 +0200 +++ /var/tmp/diff_new_pack.22Ehxm/_new 2018-05-30 12:27:08.886607369 +0200 @@ -1,7 +1,7 @@ # # spec file for package ghc-scientific # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,12 +19,12 @@ %global pkg_name scientific %bcond_with tests Name: ghc-%{pkg_name} -Version: 0.3.5.1 +Version: 0.3.6.2 Release: 0 Summary: Numbers represented using scientific notation License: BSD-3-Clause -Group: Development/Languages/Other -Url: https://hackage.haskell.org/package/%{pkg_name} +Group: Development/Libraries/Haskell +URL: https://hackage.haskell.org/package/%{pkg_name} Source0: https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz BuildRequires: ghc-Cabal-devel BuildRequires: ghc-binary-devel @@ -36,7 +36,6 @@ BuildRequires: ghc-primitive-devel BuildRequires: ghc-rpm-macros BuildRequires: ghc-text-devel -BuildRoot: %{_tmppath}/%{name}-%{version}-build %if %{with tests} BuildRequires: ghc-QuickCheck-devel BuildRequires: ghc-smallcheck-devel @@ -48,7 +47,7 @@ %endif %description -'Data.Scientific' provides the number type 'Scientific'. Scientific numbers are +"Data.Scientific" provides the number type 'Scientific'. Scientific numbers are arbitrary precision and space efficient. They are represented using <http://en.wikipedia.org/wiki/Scientific_notation scientific notation>. The implementation uses a coefficient 'c :: 'Integer'' and a base-10 exponent @@ -71,7 +70,7 @@ '1e1000000000 :: 'Rational'' will fill up all space and crash your program. Scientific works as expected: -> > read "1e1000000000" :: Scientific > 1.0e1000000000 +>>> read "1e1000000000" :: Scientific 1.0e1000000000 * Also, the space usage of converting scientific numbers with huge exponents to ''Integral's' (like: 'Int') or ''RealFloat's' (like: 'Double' or 'Float') will @@ -79,7 +78,7 @@ %package devel Summary: Haskell %{pkg_name} library development files -Group: Development/Libraries/Other +Group: Development/Libraries/Haskell Requires: %{name} = %{version}-%{release} Requires: ghc-compiler = %{ghc_version} Requires(post): ghc-compiler = %{ghc_version} @@ -107,11 +106,9 @@ %ghc_pkg_recache %files -f %{name}.files -%defattr(-,root,root,-) -%doc LICENSE +%license LICENSE %files devel -f %{name}-devel.files -%defattr(-,root,root,-) %doc changelog %changelog ++++++ scientific-0.3.5.1.tar.gz -> scientific-0.3.6.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scientific-0.3.5.1/changelog new/scientific-0.3.6.2/changelog --- old/scientific-0.3.5.1/changelog 2017-07-08 00:06:03.000000000 +0200 +++ new/scientific-0.3.6.2/changelog 2018-05-08 01:38:26.000000000 +0200 @@ -1,3 +1,58 @@ +0.3.6.2 + * Due to a regression introduced in 0.3.4.14 the RealFrac methods + and floatingOrInteger became vulnerable to a space blowup when + applied to scientifics with huge exponents. This has now been + fixed again. + +0.3.6.1 + * Fix build on GHC < 8. + +0.3.6.0 + * Make the methods of the Hashable, Eq and Ord instances safe to + use when applied to scientific numbers coming from untrusted + sources. Previously these methods first converted their arguments + to Rational before applying the operation. This is unsafe because + converting a Scientific to a Rational could fill up all space and + crash your program when the Scientific has a huge base10Exponent. + + Do note that the hash computation of the Hashable Scientific + instance has been changed because of this improvement! + + Thanks to Tom Sydney Kerckhove (@NorfairKing) for pushing me to + fix this. + + * fromRational :: Rational -> Scientific now throws an error + instead of diverging when applied to a repeating decimal. This + does mean it will consume space linear in the number of digits of + the resulting scientific. This makes "fromRational" and the other + Fractional methods "recip" and "/" a bit safer to use. + + * To get the old unsafe but more efficient behaviour the following + function was added: unsafeFromRational :: Rational -> Scientific. + + * Add alternatives for fromRationalRepetend: + + fromRationalRepetendLimited + :: Int -- ^ limit + -> Rational + -> Either (Scientific, Rational) + (Scientific, Maybe Int) + + and: + + fromRationalRepetendUnlimited + :: Rational -> (Scientific, Maybe Int) + + Thanks to Ian Jeffries (@seagreen) for the idea. + +0.3.5.3 + * Dropped upper version bounds of dependencies + because it's to much work to maintain. + +0.3.5.2 + * Remove unused ghc-prim dependency. + * Added unit tests for read and scientificP + 0.3.5.1 * Replace use of Vector from vector with Array from primitive. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scientific-0.3.5.1/scientific.cabal new/scientific-0.3.6.2/scientific.cabal --- old/scientific-0.3.5.1/scientific.cabal 2017-07-08 00:06:03.000000000 +0200 +++ new/scientific-0.3.6.2/scientific.cabal 2018-05-08 01:38:26.000000000 +0200 @@ -1,8 +1,8 @@ name: scientific -version: 0.3.5.1 +version: 0.3.6.2 synopsis: Numbers represented using scientific notation description: - @Data.Scientific@ provides the number type 'Scientific'. Scientific numbers are + "Data.Scientific" provides the number type 'Scientific'. Scientific numbers are arbitrary precision and space efficient. They are represented using <http://en.wikipedia.org/wiki/Scientific_notation scientific notation>. The implementation uses a coefficient @c :: 'Integer'@ and a base-10 exponent @@ -25,8 +25,8 @@ @1e1000000000 :: 'Rational'@ will fill up all space and crash your program. Scientific works as expected: . - > > read "1e1000000000" :: Scientific - > 1.0e1000000000 + >>> read "1e1000000000" :: Scientific + 1.0e1000000000 . * Also, the space usage of converting scientific numbers with huge exponents to @'Integral's@ (like: 'Int') or @'RealFloat's@ (like: 'Double' or 'Float') @@ -45,6 +45,13 @@ extra-source-files: changelog +Tested-With: GHC == 7.6.3 + , GHC == 7.8.4 + , GHC == 7.10.3 + , GHC == 8.0.2 + , GHC == 8.2.2 + , GHC == 8.4.1 + source-repository head type: git location: git://github.com/basvandijk/scientific.git @@ -66,21 +73,20 @@ Utils other-extensions: DeriveDataTypeable, BangPatterns ghc-options: -Wall - build-depends: base >= 4.3 && < 4.11 - , ghc-prim - , integer-logarithms >= 1 && <1.1 - , deepseq >= 1.3 && < 1.5 - , text >= 0.8 && < 1.3 - , hashable >= 1.1.2 && < 1.3 - , primitive >= 0.1 && < 0.7 - , containers >= 0.1 && < 0.6 - , binary >= 0.4.1 && < 0.9 + build-depends: base >= 4.3 && < 5 + , integer-logarithms >= 1 + , deepseq >= 1.3 + , text >= 0.8 + , hashable >= 1.1.2 + , primitive >= 0.1 + , containers >= 0.1 + , binary >= 0.4.1 if flag(bytestring-builder) build-depends: bytestring >= 0.9 && < 0.10.4 , bytestring-builder >= 0.10.4 && < 0.11 else - build-depends: bytestring >= 0.10.4 && < 0.11 + build-depends: bytestring >= 0.10.4 if flag(integer-simple) build-depends: integer-simple @@ -98,22 +104,22 @@ ghc-options: -Wall build-depends: scientific - , base >= 4.3 && < 4.11 - , binary >= 0.4.1 && < 0.9 - , tasty >= 0.5 && < 0.12 - , tasty-ant-xml >= 1.0 && < 1.2 - , tasty-hunit >= 0.8 && < 0.10 - , tasty-smallcheck >= 0.2 && < 0.9 - , tasty-quickcheck >= 0.8 && < 0.10 - , smallcheck >= 1.0 && < 1.2 - , QuickCheck >= 2.5 && < 2.11 - , text >= 0.8 && < 1.3 + , base >= 4.3 && < 5 + , binary >= 0.4.1 + , tasty >= 0.5 + , tasty-ant-xml >= 1.0 + , tasty-hunit >= 0.8 + , tasty-smallcheck >= 0.2 + , tasty-quickcheck >= 0.8 + , smallcheck >= 1.0 + , QuickCheck >= 2.5 + , text >= 0.8 if flag(bytestring-builder) build-depends: bytestring >= 0.9 && < 0.10.4 , bytestring-builder >= 0.10.4 && < 0.11 else - build-depends: bytestring >= 0.10.4 && < 0.11 + build-depends: bytestring >= 0.10.4 benchmark bench-scientific type: exitcode-stdio-1.0 @@ -122,5 +128,5 @@ default-language: Haskell2010 ghc-options: -O2 build-depends: scientific - , base >= 4.3 && < 4.11 - , criterion >= 0.5 && < 1.3 + , base >= 4.3 && < 5 + , criterion >= 0.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scientific-0.3.5.1/src/Data/Scientific.hs new/scientific-0.3.6.2/src/Data/Scientific.hs --- old/scientific-0.3.5.1/src/Data/Scientific.hs 2017-07-08 00:06:03.000000000 +0200 +++ new/scientific-0.3.6.2/src/Data/Scientific.hs 2018-05-08 01:38:26.000000000 +0200 @@ -22,6 +22,11 @@ -- aren't truly arbitrary precision. I intend to change the type of the exponent -- to 'Integer' in a future release. -- +-- /WARNING:/ Although @Scientific@ has instances for all numeric classes the +-- methods should be used with caution when applied to scientific numbers coming +-- from untrusted sources. See the warnings of the instances belonging to +-- 'Scientific'. +-- -- The main application of 'Scientific' is to be used as the target of parsing -- arbitrary precision numbers coming from an untrusted source. The advantages -- over using 'Rational' for this are that: @@ -41,13 +46,6 @@ -- 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. Consider using 'fromRationalRepetend' for these --- rationals which will detect the repetition and indicate where it starts. --- -- This module is designed to be imported qualified: -- -- @import Data.Scientific as Scientific@ @@ -66,8 +64,14 @@ , isInteger -- * Conversions + -- ** Rational + , unsafeFromRational , fromRationalRepetend + , fromRationalRepetendLimited + , fromRationalRepetendUnlimited , toRationalRepetend + + -- ** Floating & integer , floatingOrInteger , toRealFloat , toBoundedRealFloat @@ -99,7 +103,6 @@ import Data.Binary (Binary, get, put) import Data.Char (intToDigit, ord) import Data.Data (Data) -import Data.Function (on) import Data.Hashable (Hashable(..)) import Data.Int (Int8, Int16, Int32, Int64) import qualified Data.Map as M (Map, empty, insert, lookup) @@ -116,6 +119,10 @@ import Text.ParserCombinators.ReadP ( ReadP ) import Data.Text.Lazy.Builder.RealFloat (FPFormat(..)) +#if !MIN_VERSION_base(4,9,0) +import Control.Applicative ((*>)) +#endif + #if !MIN_VERSION_base(4,8,0) import Data.Functor ((<$>)) import Data.Word (Word) @@ -174,42 +181,63 @@ instance NFData Scientific where rnf (Scientific _ _) = () +-- | A hash can be safely calculated from a @Scientific@. No magnitude @10^e@ is +-- calculated so there's no risk of a blowup in space or time when hashing +-- scientific numbers coming from untrusted sources. instance Hashable Scientific where - hashWithSalt salt = hashWithSalt salt . toRational + hashWithSalt salt s = salt `hashWithSalt` c `hashWithSalt` e + where + Scientific c e = normalize s +-- | Note that in the future I intend to change the type of the 'base10Exponent' +-- from @Int@ to @Integer@. To be forward compatible the @Binary@ instance +-- already encodes the exponent as 'Integer'. instance Binary Scientific where - put (Scientific c e) = do - put c - -- In the future I intend to change the type of the base10Exponent e from - -- Int to Integer. To support backward compatability I already convert e - -- to Integer here: - put $ toInteger e - + put (Scientific c e) = put c *> put (toInteger e) get = Scientific <$> get <*> (fromInteger <$> get) +-- | Scientific numbers can be safely compared for equality. 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 Eq Scientific where - (==) = (==) `on` toRational - {-# INLINABLE (==) #-} - - (/=) = (/=) `on` toRational - {-# INLINABLE (/=) #-} + s1 == s2 = c1 == c2 && e1 == e2 + where + Scientific c1 e1 = normalize s1 + Scientific c2 e2 = normalize s2 +-- | 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 - (<) = (<) `on` toRational - {-# INLINABLE (<) #-} - - (<=) = (<=) `on` toRational - {-# INLINABLE (<=) #-} + 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 - (>) = (>) `on` toRational - {-# INLINABLE (>) #-} + 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 - (>=) = (>=) `on` toRational - {-# INLINABLE (>=) #-} + log10cx = integerLog10' cx + log10cy = integerLog10' cy - compare = compare `on` toRational - {-# INLINABLE compare #-} + d = log10cx - log10cy +-- | /WARNING:/ '+' and '-' compute the 'Integer' magnitude: @10^e@ where @e@ is +-- the difference between the @'base10Exponent's@ of the arguments. If these +-- methods are applied to arguments which have huge exponents this could fill up +-- all space and crash your program! So don't apply these methods to scientific +-- numbers coming from untrusted sources. The other methods can be used safely. instance Num Scientific where Scientific c1 e1 + Scientific c2 e2 | e1 < e2 = Scientific (c1 + c2*l) e1 @@ -264,12 +292,12 @@ "realToFrac_toRealFloat_Float" realToFrac = toRealFloat :: Scientific -> Float #-} --- | /WARNING:/ 'recip' and '/' will diverge (i.e. loop and consume all space) --- when their outputs are <https://en.wikipedia.org/wiki/Repeating_decimal repeating decimals>. +-- | /WARNING:/ 'recip' and '/' will throw an error when their outputs are +-- <https://en.wikipedia.org/wiki/Repeating_decimal repeating decimals>. -- --- 'fromRational' will diverge when the input 'Rational' is a repeating decimal. --- Consider using 'fromRationalRepetend' for these rationals which will detect --- the repetition and indicate where it starts. +-- 'fromRational' will throw an error when the input 'Rational' is a repeating +-- decimal. Consider using 'fromRationalRepetend' for these rationals which +-- will detect the repetition and indicate where it starts. instance Fractional Scientific where recip = fromRational . recip . toRational {-# INLINABLE recip #-} @@ -277,23 +305,45 @@ x / y = fromRational $ toRational x / toRational y {-# INLINABLE (/) #-} - fromRational rational - | d == 0 = throw DivideByZero - | otherwise = positivize (longDiv 0 0) (numerator rational) + fromRational rational = + case mbRepetendIx of + Nothing -> s + Just _ix -> error $ + "fromRational has been applied to a repeating decimal " ++ + "which can't be represented as a Scientific! " ++ + "It's better to avoid performing fractional operations on Scientifics " ++ + "and convert them to other fractional types like Double as early as possible." where - -- Divide the numerator by the denominator using long division. - longDiv :: Integer -> Int -> (Integer -> Scientific) - longDiv !c !e 0 = Scientific c e - longDiv !c !e !n - -- TODO: Use a logarithm here! - | n < d = longDiv (c * 10) (e - 1) (n * 10) - | otherwise = case n `quotRemInteger` d of - (#q, r#) -> longDiv (c + q) e r + (s, mbRepetendIx) = fromRationalRepetendUnlimited rational - d = denominator rational - --- | Like 'fromRational', this function converts a `Rational` to a `Scientific` --- but instead of diverging (i.e loop and consume all space) on +-- | Although 'fromRational' is unsafe because it will throw errors on +-- <https://en.wikipedia.org/wiki/Repeating_decimal repeating decimals>, +-- @unsafeFromRational@ is even more unsafe because it will diverge instead (i.e +-- loop and consume all space). Though it will be more efficient because it +-- doesn't need to consume space linear in the number of digits in the resulting +-- scientific to detect the repetition. +-- +-- Consider using 'fromRationalRepetend' for these rationals which will detect +-- the repetition and indicate where it starts. +unsafeFromRational :: Rational -> Scientific +unsafeFromRational rational + | d == 0 = throw DivideByZero + | otherwise = positivize (longDiv 0 0) (numerator rational) + where + -- Divide the numerator by the denominator using long division. + longDiv :: Integer -> Int -> (Integer -> Scientific) + longDiv !c !e 0 = Scientific c e + longDiv !c !e !n + -- TODO: Use a logarithm here! + | n < d = longDiv (c * 10) (e - 1) (n * 10) + | otherwise = case n `quotRemInteger` d of + (#q, r#) -> longDiv (c + q) e r + + d = denominator rational + +-- | Like 'fromRational' and 'unsafeFromRational', this function converts a +-- `Rational` to a `Scientific` but instead of failing or diverging (i.e loop +-- and consume all space) on -- <https://en.wikipedia.org/wiki/Repeating_decimal repeating decimals> -- it detects the repeating part, the /repetend/, and returns where it starts. -- @@ -335,7 +385,18 @@ -> Rational -> Either (Scientific, Rational) (Scientific, Maybe Int) -fromRationalRepetend mbLimit rational +fromRationalRepetend mbLimit rational = + case mbLimit of + Nothing -> Right $ fromRationalRepetendUnlimited rational + Just l -> fromRationalRepetendLimited l rational + +-- | Like 'fromRationalRepetend' but always accepts a limit. +fromRationalRepetendLimited + :: Int -- ^ limit + -> Rational + -> Either (Scientific, Rational) + (Scientific, Maybe Int) +fromRationalRepetendLimited l rational | d == 0 = throw DivideByZero | num < 0 = case longDiv (-num) of Left (s, r) -> Left (-s, -r) @@ -345,11 +406,38 @@ num = numerator rational longDiv :: Integer -> Either (Scientific, Rational) (Scientific, Maybe Int) - longDiv n = case mbLimit of - Nothing -> Right $ longDivNoLimit 0 0 M.empty n - Just l -> longDivWithLimit (-l) n + longDiv = longDivWithLimit 0 0 M.empty + + longDivWithLimit + :: Integer + -> Int + -> M.Map Integer Int + -> (Integer -> Either (Scientific, Rational) + (Scientific, Maybe Int)) + longDivWithLimit !c !e _ns 0 = Right (Scientific c e, Nothing) + longDivWithLimit !c !e ns !n + | Just e' <- M.lookup n ns = Right (Scientific c e, Just (-e')) + | e <= (-l) = Left (Scientific c e, n % (d * magnitude (-e))) + | n < d = let !ns' = M.insert n e ns + in longDivWithLimit (c * 10) (e - 1) ns' (n * 10) + | otherwise = case n `quotRemInteger` d of + (#q, r#) -> longDivWithLimit (c + q) e ns r + + d = denominator rational + +-- | Like 'fromRationalRepetend' but doesn't accept a limit. +fromRationalRepetendUnlimited :: Rational -> (Scientific, Maybe Int) +fromRationalRepetendUnlimited rational + | d == 0 = throw DivideByZero + | num < 0 = case longDiv (-num) of + (s, mb) -> (-s, mb) + | otherwise = longDiv num + where + num = numerator rational + + longDiv :: Integer -> (Scientific, Maybe Int) + longDiv = longDivNoLimit 0 0 M.empty - -- Divide the numerator by the denominator using long division. longDivNoLimit :: Integer -> Int -> M.Map Integer Int @@ -362,22 +450,6 @@ | otherwise = case n `quotRemInteger` d of (#q, r#) -> longDivNoLimit (c + q) e ns r - longDivWithLimit :: Int -> Integer -> Either (Scientific, Rational) (Scientific, Maybe Int) - longDivWithLimit l = go 0 0 M.empty - where - go :: Integer - -> Int - -> M.Map Integer Int - -> (Integer -> Either (Scientific, Rational) (Scientific, Maybe Int)) - go !c !e _ns 0 = Right (Scientific c e, Nothing) - go !c !e ns !n - | Just e' <- M.lookup n ns = Right (Scientific c e, Just (-e')) - | e <= l = Left (Scientific c e, n % (d * magnitude (-e))) - | n < d = let !ns' = M.insert n e ns - in go (c * 10) (e - 1) ns' (n * 10) - | otherwise = case n `quotRemInteger` d of - (#q, r#) -> go (c + q) e ns r - d = denominator rational -- | @@ -393,6 +465,11 @@ -- -- * @r < -(base10Exponent s)@ -- +-- /WARNING:/ @toRationalRepetend@ needs to compute the 'Integer' magnitude: +-- @10^^n@. Where @n@ is based on the 'base10Exponent` of the scientific. If +-- applied to a huge exponent this could fill up all space and crash your +-- program! So don't apply this function to untrusted input. +-- -- The formula to convert the @Scientific@ @s@ -- with a repetend starting at index @r@ is described in the paper: -- <http://fiziko.bureau42.com/teaching_tidbits/turning_repeating_decimals_into_fractions.pdf turning_repeating_decimals_into_fractions.pdf> @@ -443,6 +520,10 @@ nines = m - 1 +-- | /WARNING:/ the methods of the @RealFrac@ instance need to compute the +-- magnitude @10^e@. If applied to a huge exponent this could take a long +-- time. Even worse, when the destination type is unbounded (i.e. 'Integer') it +-- could fill up all space and crash your program! instance RealFrac Scientific where -- | The function 'properFraction' takes a Scientific number @s@ -- and returns a pair @(n,f)@ such that @s = n+f@, and: @@ -566,7 +647,7 @@ -- | Precondition: the 'Scientific' @s@ needs to be an integer: -- @base10Exponent (normalize s) >= 0@ toIntegral :: (Num a) => Scientific -> a -toIntegral (Scientific c e) = fromInteger c * fromInteger (magnitude e) +toIntegral (Scientific c e) = fromInteger c * magnitude e {-# INLINE toIntegral #-} @@ -603,11 +684,11 @@ uninitialised = error "Data.Scientific: uninitialised element" -- | @magnitude e == 10 ^ e@ -magnitude :: Int -> Integer +magnitude :: Num a => Int -> a magnitude e | e < maxExpt = cachedPow10 e | otherwise = cachedPow10 hi * 10 ^ (e - hi) where - cachedPow10 = Primitive.indexArray expts10 + cachedPow10 = fromInteger . Primitive.indexArray expts10 hi = maxExpt - 1 @@ -754,9 +835,20 @@ {-# SPECIALIZE toBoundedInteger :: Scientific -> Maybe Word32 #-} {-# SPECIALIZE toBoundedInteger :: Scientific -> Maybe Word64 #-} --- | @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'. +-- | @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' and wrapped in 'Left'. +-- +-- In case it's integer to scientific is converted to the desired 'Integral' and +-- wrapped in 'Right'. +-- +-- /WARNING:/ To convert the scientific to an integral the magnitude @10^e@ +-- needs to be computed. If applied to a huge exponent this could take a long +-- time. Even worse, when the destination type is unbounded (i.e. 'Integer') it +-- could fill up all space and crash your program! So don't apply this function +-- to untrusted input but use 'toBoundedInteger' instead. -- -- Also see: 'isFloating' or 'isInteger'. floatingOrInteger :: (RealFloat r, Integral i) => Scientific -> Either r i @@ -885,6 +977,7 @@ -- Pretty Printing ---------------------------------------------------------------------- +-- | See 'formatScientific' if you need more control over the rendering. instance Show Scientific where show s | coefficient s < 0 = '-':showPositive (-s) | otherwise = showPositive s diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scientific-0.3.5.1/test/test.hs new/scientific-0.3.6.2/test/test.hs --- old/scientific-0.3.5.1/test/test.hs 2017-07-08 00:06:03.000000000 +0200 +++ new/scientific-0.3.6.2/test/test.hs 2018-05-08 01:38:26.000000000 +0200 @@ -19,7 +19,7 @@ import Data.Scientific as Scientific import Test.Tasty import Test.Tasty.Runners.AntXML -import Test.Tasty.HUnit (testCase, (@?=), Assertion) +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) @@ -34,10 +34,60 @@ import qualified Data.ByteString.Lazy.Char8 as BLC8 import qualified Data.ByteString.Builder.Scientific as B import qualified Data.ByteString.Builder as B +import Text.ParserCombinators.ReadP (readP_to_S) main :: IO () main = testMain $ testGroup "scientific" - [ smallQuick "normalization" + [ testGroup "DoS protection" + [ testGroup "Eq" + [ testCase "1e1000000" $ assertBool "" $ + (read "1e1000000" :: Scientific) == (read "1e1000000" :: Scientific) + ] + , testGroup "Ord" + [ testCase "compare 1234e1000000 123e1000001" $ + compare (read "1234e1000000" :: Scientific) (read "123e1000001" :: Scientific) @?= GT + ] + + , testGroup "RealFrac" + [ testGroup "floor" + [ testCase "1e1000000" $ (floor (read "1e1000000" :: Scientific) :: Int) @?= 0 + , testCase "-1e-1000000" $ (floor (read "-1e-1000000" :: Scientific) :: Int) @?= (-1) + , testCase "1e-1000000" $ (floor (read "1e-1000000" :: Scientific) :: Int) @?= 0 + ] + , testGroup "ceiling" + [ testCase "1e1000000" $ (ceiling (read "1e1000000" :: Scientific) :: Int) @?= 0 + , testCase "-1e-1000000" $ (ceiling (read "-1e-1000000" :: Scientific) :: Int) @?= 0 + , testCase "1e-1000000" $ (ceiling (read "1e-1000000" :: Scientific) :: Int) @?= 1 + ] + , testGroup "round" + [ testCase "1e1000000" $ (round (read "1e1000000" :: Scientific) :: Int) @?= 0 + , testCase "-1e-1000000" $ (round (read "-1e-1000000" :: Scientific) :: Int) @?= 0 + , testCase "1e-1000000" $ (round (read "1e-1000000" :: Scientific) :: Int) @?= 0 + ] + , testGroup "truncate" + [ testCase "1e1000000" $ (truncate (read "1e1000000" :: Scientific) :: Int) @?= 0 + , testCase "-1e-1000000" $ (truncate (read "-1e-1000000" :: Scientific) :: Int) @?= 0 + , testCase "1e-1000000" $ (truncate (read "1e-1000000" :: Scientific) :: Int) @?= 0 + ] + , testGroup "properFracton" + [ testCase "1e1000000" $ properFraction (read "1e1000000" :: Scientific) @?= (0 :: Int, 0) + , testCase "-1e-1000000" $ let s = read "-1e-1000000" :: Scientific + in properFraction s @?= (0 :: Int, s) + , testCase "1e-1000000" $ let s = read "1e-1000000" :: Scientific + in properFraction s @?= (0 :: Int, s) + ] + ] + , testGroup "toRealFloat" + [ testCase "1e1000000" $ assertBool "Should be infinity!" $ isInfinite $ + (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) (QC.forAll normalizedScientificGen $ \s -> @@ -55,6 +105,11 @@ , testCase "reads \"(1.3 )\"" $ testReads "(1.3 )" [(1.3, "")] , testCase "reads \"((1.3))\"" $ testReads "((1.3))" [(1.3, "")] , testCase "reads \" 1.3\"" $ testReads " 1.3" [(1.3, "")] + , testCase "read \" ( (( -1.0e+3 ) ))\"" $ testRead " ( (( -1.0e+3 ) ))" (-1000.0) + , testCase "scientificP \"3\"" $ testScientificP "3" [(3.0, "")] + , testCase "scientificP \"3.0e2\"" $ testScientificP "3.0e2" [(3.0, "e2"), (300.0, "")] + , testCase "scientificP \"+3.0e+2\"" $ testScientificP "+3.0e+2" [(3.0, "e+2"), (300.0, "")] + , testCase "scientificP \"-3.0e-2\"" $ testScientificP "-3.0e-2" [(-3.0, "e-2"), (-3.0e-2, "")] ] , testGroup "Formatting" @@ -91,6 +146,17 @@ -- show d ] + , testGroup "Eq" + [ testProperty "==" $ \(s1 :: Scientific) (s2 :: Scientific) -> + (s1 == s2) == (toRational s1 == toRational s2) + , testProperty "s == s" $ \(s :: Scientific) -> s == s + ] + + , testGroup "Ord" + [ testProperty "compare" $ \(s1 :: Scientific) (s2 :: Scientific) -> + compare s1 s2 == compare (toRational s1) (toRational s2) + ] + , testGroup "Num" [ testGroup "Equal to Rational" [ testProperty "fromInteger" $ \i -> fromInteger i === fromRational (fromInteger i) @@ -218,6 +284,12 @@ testReads :: String -> [(Scientific, String)] -> Assertion testReads inp out = reads inp @?= out +testRead :: String -> Scientific -> Assertion +testRead inp out = read inp @?= out + +testScientificP :: String -> [(Scientific, String)] -> Assertion +testScientificP inp out = readP_to_S Scientific.scientificP inp @?= out + genericIsFloating :: RealFrac a => a -> Bool genericIsFloating a = fromInteger (floor a :: Integer) /= a
