scientific-notation 0.1.6.1 → 0.1.7.0
raw patch · 4 files changed
+68/−5 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Number.Scientific: toInteger :: Scientific -> Maybe Integer
Files
- CHANGELOG.md +4/−0
- scientific-notation.cabal +3/−2
- src/Data/Number/Scientific.hs +50/−2
- test/Main.hs +11/−1
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for scientific-notation +## 0.1.7.0 -- 2024-02-12++* Add `toInteger`.+ ## 0.1.6.1 -- 2024-02-06 * Update package metadata.
scientific-notation.cabal view
@@ -1,6 +1,6 @@-cabal-version: 2.2+cabal-version: 2.4 name: scientific-notation-version: 0.1.6.1+version: 0.1.7.0 synopsis: Scientific notation intended for tokenization description: This library provides a type used to represent a number in@@ -45,6 +45,7 @@ copyright: 2019 Andrew Martin category: Data extra-doc-files: CHANGELOG.md+tested-with: GHC ==9.4.8 || ==9.6.3 || ==9.8.1 common build-settings default-language: Haskell2010
src/Data/Number/Scientific.hs view
@@ -34,6 +34,7 @@ , toInt , toInt32 , toInt64+ , toInteger , withExposed -- * Scale and Consume@@ -59,7 +60,7 @@ , builderUtf8 ) where -import Prelude hiding (negate)+import Prelude hiding (negate,toInteger) import Control.Monad.ST (runST) import Data.ByteString.Short.Internal (ShortByteString (SBS))@@ -237,6 +238,53 @@ (# (# #) | #) -> Nothing (# | i #) -> Just (I64# (intToInt64# i)) +-- | This can exhaust memory. Do not use on untrusted input.+toInteger :: Scientific -> Maybe Integer+toInteger (Scientific coeff e largeNum)+ | e == minBound = case largeNum of+ LargeScientific bigCoeff bigExp -> case compare bigExp 0 of+ GT -> Just (bigCoeff * ((10 :: Integer) ^ bigExp))+ EQ -> Just bigCoeff+ LT -> case attemptLargeNegativeExponentiate bigCoeff (Prelude.negate bigExp) of+ Nothing -> Nothing+ Just i -> Just i+ | otherwise = case compare e 0 of+ GT -> Just (Prelude.toInteger coeff * ((10 :: Integer) ^ e))+ EQ -> Just (Prelude.toInteger coeff)+ LT -> case attemptNegativeExponentiate coeff (Prelude.negate e) of+ Nothing -> Nothing+ Just i -> Just (Prelude.toInteger i)++-- The exponent argument must be non-negative, but we interpret it as+-- a negative number.+attemptNegativeExponentiate :: Int -> Int -> Maybe Int+attemptNegativeExponentiate c0 e0 = go c0 e0 where+ -- Note: This is unoptimized and has poor performance.+ go :: Int -> Int -> Maybe Int+ go !c !e = case compare e 0 of+ EQ -> Just c+ GT ->+ let c' = div c 10+ in if c' * 10 == c+ then go c' (e - 1)+ else Nothing+ LT -> errorWithoutStackTrace "attemptNegativeExponentiate: invariant violated"++-- The exponent argument must be non-negative, but we interpret it as+-- a negative number.+attemptLargeNegativeExponentiate :: Integer -> Integer -> Maybe Integer+attemptLargeNegativeExponentiate c0 e0 = go c0 e0 where+ -- Note: This is unoptimized and has poor performance.+ go :: Integer -> Integer -> Maybe Integer+ go !c !e = case compare e 0 of+ EQ -> Just c+ GT ->+ let c' = div c 10+ in if c' * 10 == c+ then go c' (e - 1)+ else Nothing+ LT -> errorWithoutStackTrace "attemptLargeNegativeExponentiate: invariant violated"+ {- | This works even if the number has a fractional component. For example: >>> roundShiftedToInt64 2 (fromFixed @E3 1.037)@@ -699,7 +747,7 @@ else roundNegIntegerNegExp10 coefficient (fromInteger expon) | otherwise = (# (# #) | #) where- exponent1 = exponent0 + toInteger adj+ exponent1 = exponent0 + Prelude.toInteger adj -- Precondition: the exponent is non-negative. This returns -- an unboxed Nothing on overflow. This implementation should
test/Main.hs view
@@ -4,13 +4,15 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +import Prelude hiding (toInteger)+ import Control.Monad (replicateM, when) import Data.Bool (bool) import Data.Bytes.Types (Bytes (Bytes)) import Data.Char (ord) import Data.Fixed (E12, Fixed) import Data.Int (Int64)-import Data.Number.Scientific (large, roundShiftedToInt64, small, toInt32, toInt64, toWord16, toWord32, toWord64, toWord8)+import Data.Number.Scientific (large, roundShiftedToInt64, small, toInt32, toInt64, toWord16, toWord32, toWord64, toWord8, toInteger) import Data.Primitive (ByteArray) import Data.Word (Word8) import Test.Tasty (TestTree, defaultMain, testGroup)@@ -146,6 +148,14 @@ , THU.testCase "AD" $ Just 0 @=? roundShiftedToInt64 0 (large (50000000000000000000000000000) (-1_000_000_000)) , THU.testCase "AE" $ Just 2 @=? toInt64 (small 2 0) , THU.testCase "AF" $ Just 2 @=? toInt64 (large 2 0)+ ]+ , testGroup+ "Integer"+ [ THU.testCase "A" $ Just 30 @=? toInteger (small 300 (-1))+ , THU.testCase "B" $ Just 300 @=? toInteger (small 300 0)+ , THU.testCase "C" $ Just 65535 @=? toInteger (large 65535e40 (-40))+ , THU.testCase "D" $ Just 999_999_999_999_999_999_999_999_999_999_999_000 @=? toInteger (large 999_999_999_999_999_999_999_999_999_999_999 3)+ , THU.testCase "E" $ Just 999_999_999_999_999_999_999_999_999_999_999 @=? toInteger (large 999_999_999_999_999_999_999_999_999_999_999_000 (-3)) ] , testGroup "Compare"