HasBigDecimal (empty) → 0.1.1
raw patch · 9 files changed
+991/−0 lines, 9 filesdep +HasBigDecimaldep +QuickCheckdep +base
Dependencies added: HasBigDecimal, QuickCheck, base, hspec
Files
- HasBigDecimal.cabal +37/−0
- LICENSE +201/−0
- README.md +59/−0
- src/Data/BigDecimal.hs +214/−0
- src/Data/BigFloating.hs +100/−0
- test/Data/BigDecimalSpec.hs +315/−0
- test/Data/BigFloatingSpec.hs +45/−0
- test/Data/TestUtils.hs +19/−0
- test/Spec.hs +1/−0
+ HasBigDecimal.cabal view
@@ -0,0 +1,37 @@+name: HasBigDecimal+version: 0.1.1+synopsis: A library for arbitrary precision decimal numbers.+description: A native Haskell implementation of arbitrary precicion decimal numbers, based on Haskell Integers. Inspired by Java BigDecimals++homepage: https://github.com/thma/HasBigDecimal#readme+license: Apache-2.0+license-file: LICENSE+author: Thomas Mahler+maintainer: thma@apache.org+copyright: 2018 Thomas Mahler+category: Math+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Data.BigDecimal, Data.BigFloating+ build-depends: base >= 4.7 && < 5+ default-language: Haskell2010++test-suite HasBigDecimal-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: Data.BigDecimalSpec, Data.BigFloatingSpec, Data.TestUtils+ build-depends: base+ , HasBigDecimal+ , hspec+ , QuickCheck+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/thma/HasBigDecimal
+ LICENSE view
@@ -0,0 +1,201 @@+ Apache License+ Version 2.0, January 2004+ http://www.apache.org/licenses/++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION++ 1. Definitions.++ "License" shall mean the terms and conditions for use, reproduction,+ and distribution as defined by Sections 1 through 9 of this document.++ "Licensor" shall mean the copyright owner or entity authorized by+ the copyright owner that is granting the License.++ "Legal Entity" shall mean the union of the acting entity and all+ other entities that control, are controlled by, or are under common+ control with that entity. For the purposes of this definition,+ "control" means (i) the power, direct or indirect, to cause the+ direction or management of such entity, whether by contract or+ otherwise, or (ii) ownership of fifty percent (50%) or more of the+ outstanding shares, or (iii) beneficial ownership of such entity.++ "You" (or "Your") shall mean an individual or Legal Entity+ exercising permissions granted by this License.++ "Source" form shall mean the preferred form for making modifications,+ including but not limited to software source code, documentation+ source, and configuration files.++ "Object" form shall mean any form resulting from mechanical+ transformation or translation of a Source form, including but+ not limited to compiled object code, generated documentation,+ and conversions to other media types.++ "Work" shall mean the work of authorship, whether in Source or+ Object form, made available under the License, as indicated by a+ copyright notice that is included in or attached to the work+ (an example is provided in the Appendix below).++ "Derivative Works" shall mean any work, whether in Source or Object+ form, that is based on (or derived from) the Work and for which the+ editorial revisions, annotations, elaborations, or other modifications+ represent, as a whole, an original work of authorship. For the purposes+ of this License, Derivative Works shall not include works that remain+ separable from, or merely link (or bind by name) to the interfaces of,+ the Work and Derivative Works thereof.++ "Contribution" shall mean any work of authorship, including+ the original version of the Work and any modifications or additions+ to that Work or Derivative Works thereof, that is intentionally+ submitted to Licensor for inclusion in the Work by the copyright owner+ or by an individual or Legal Entity authorized to submit on behalf of+ the copyright owner. For the purposes of this definition, "submitted"+ means any form of electronic, verbal, or written communication sent+ to the Licensor or its representatives, including but not limited to+ communication on electronic mailing lists, source code control systems,+ and issue tracking systems that are managed by, or on behalf of, the+ Licensor for the purpose of discussing and improving the Work, but+ excluding communication that is conspicuously marked or otherwise+ designated in writing by the copyright owner as "Not a Contribution."++ "Contributor" shall mean Licensor and any individual or Legal Entity+ on behalf of whom a Contribution has been received by Licensor and+ subsequently incorporated within the Work.++ 2. Grant of Copyright License. Subject to the terms and conditions of+ this License, each Contributor hereby grants to You a perpetual,+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable+ copyright license to reproduce, prepare Derivative Works of,+ publicly display, publicly perform, sublicense, and distribute the+ Work and such Derivative Works in Source or Object form.++ 3. Grant of Patent License. Subject to the terms and conditions of+ this License, each Contributor hereby grants to You a perpetual,+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable+ (except as stated in this section) patent license to make, have made,+ use, offer to sell, sell, import, and otherwise transfer the Work,+ where such license applies only to those patent claims licensable+ by such Contributor that are necessarily infringed by their+ Contribution(s) alone or by combination of their Contribution(s)+ with the Work to which such Contribution(s) was submitted. If You+ institute patent litigation against any entity (including a+ cross-claim or counterclaim in a lawsuit) alleging that the Work+ or a Contribution incorporated within the Work constitutes direct+ or contributory patent infringement, then any patent licenses+ granted to You under this License for that Work shall terminate+ as of the date such litigation is filed.++ 4. Redistribution. You may reproduce and distribute copies of the+ Work or Derivative Works thereof in any medium, with or without+ modifications, and in Source or Object form, provided that You+ meet the following conditions:++ (a) You must give any other recipients of the Work or+ Derivative Works a copy of this License; and++ (b) You must cause any modified files to carry prominent notices+ stating that You changed the files; and++ (c) You must retain, in the Source form of any Derivative Works+ that You distribute, all copyright, patent, trademark, and+ attribution notices from the Source form of the Work,+ excluding those notices that do not pertain to any part of+ the Derivative Works; and++ (d) If the Work includes a "NOTICE" text file as part of its+ distribution, then any Derivative Works that You distribute must+ include a readable copy of the attribution notices contained+ within such NOTICE file, excluding those notices that do not+ pertain to any part of the Derivative Works, in at least one+ of the following places: within a NOTICE text file distributed+ as part of the Derivative Works; within the Source form or+ documentation, if provided along with the Derivative Works; or,+ within a display generated by the Derivative Works, if and+ wherever such third-party notices normally appear. The contents+ of the NOTICE file are for informational purposes only and+ do not modify the License. You may add Your own attribution+ notices within Derivative Works that You distribute, alongside+ or as an addendum to the NOTICE text from the Work, provided+ that such additional attribution notices cannot be construed+ as modifying the License.++ You may add Your own copyright statement to Your modifications and+ may provide additional or different license terms and conditions+ for use, reproduction, or distribution of Your modifications, or+ for any such Derivative Works as a whole, provided Your use,+ reproduction, and distribution of the Work otherwise complies with+ the conditions stated in this License.++ 5. Submission of Contributions. Unless You explicitly state otherwise,+ any Contribution intentionally submitted for inclusion in the Work+ by You to the Licensor shall be under the terms and conditions of+ this License, without any additional terms or conditions.+ Notwithstanding the above, nothing herein shall supersede or modify+ the terms of any separate license agreement you may have executed+ with Licensor regarding such Contributions.++ 6. Trademarks. This License does not grant permission to use the trade+ names, trademarks, service marks, or product names of the Licensor,+ except as required for reasonable and customary use in describing the+ origin of the Work and reproducing the content of the NOTICE file.++ 7. Disclaimer of Warranty. Unless required by applicable law or+ agreed to in writing, Licensor provides the Work (and each+ Contributor provides its Contributions) on an "AS IS" BASIS,+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or+ implied, including, without limitation, any warranties or conditions+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A+ PARTICULAR PURPOSE. You are solely responsible for determining the+ appropriateness of using or redistributing the Work and assume any+ risks associated with Your exercise of permissions under this License.++ 8. Limitation of Liability. In no event and under no legal theory,+ whether in tort (including negligence), contract, or otherwise,+ unless required by applicable law (such as deliberate and grossly+ negligent acts) or agreed to in writing, shall any Contributor be+ liable to You for damages, including any direct, indirect, special,+ incidental, or consequential damages of any character arising as a+ result of this License or out of the use or inability to use the+ Work (including but not limited to damages for loss of goodwill,+ work stoppage, computer failure or malfunction, or any and all+ other commercial damages or losses), even if such Contributor+ has been advised of the possibility of such damages.++ 9. Accepting Warranty or Additional Liability. While redistributing+ the Work or Derivative Works thereof, You may choose to offer,+ and charge a fee for, acceptance of support, warranty, indemnity,+ or other liability obligations and/or rights consistent with this+ License. However, in accepting such obligations, You may act only+ on Your own behalf and on Your sole responsibility, not on behalf+ of any other Contributor, and only if You agree to indemnify,+ defend, and hold each Contributor harmless for any liability+ incurred by, or claims asserted against, such Contributor by reason+ of your accepting any such warranty or additional liability.++ END OF TERMS AND CONDITIONS++ APPENDIX: How to apply the Apache License to your work.++ To apply the Apache License to your work, attach the following+ boilerplate notice, with the fields enclosed by brackets "[]"+ replaced with your own identifying information. (Don't include+ the brackets!) The text should be enclosed in the appropriate+ comment syntax for the file format. We also recommend that a+ file or class name and description of purpose be included on the+ same "printed page" as the copyright notice for easier+ identification within third-party archives.++ Copyright [yyyy] [name of copyright owner]++ Licensed under the Apache License, Version 2.0 (the "License");+ you may not use this file except in compliance with the License.+ You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++ Unless required by applicable law or agreed to in writing, software+ distributed under the License is distributed on an "AS IS" BASIS,+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ See the License for the specific language governing permissions and+ limitations under the License.
+ README.md view
@@ -0,0 +1,59 @@+[](https://travis-ci.org/thma/HasBigDecimal)++# HasBigDecimal++This module defines the type 'BigDecimal' which provides a representation of arbitrary precision decimal numbers.+'BigDecimal' is a native Haskell implementation based on arbitrary sized 'Integer' values.+The implementation was inspired by Java BigDecimals.++BigDecimal instantiates the typeclasses 'Num', 'Fractional' and 'Real'. It is thus possible to use all common operators like '+', '-', '*', '/', '^' on them.++# Some examples from a ghci REPL+```haskell+λ> a = BigDecimal 144 2+λ> toString a+"1.44"+λ> b = sqrt a+λ> toString b+"1.2"+λ> b * b+BigDecimal 144 2+λ> b * b * b+BigDecimal 1728 3+λ> b^2+BigDecimal 144 2+λ> c = fromString "123.4567890"+λ> c+BigDecimal 1234567890 7+λ> a / c+BigDecimal 1166400010614240096589584878965222398584 41+λ> roundBD it (halfUp 10)+BigDecimal 116640001 10+λ> divide (a, c) $ halfUp 20+BigDecimal 1166400010614240097 20+```++# BigFloating+in addition to the pretty complete BigDecimal module there is the rather scetchy BigFloating module.+BigFloating contains a few first step to let BigDecimal instantiate the Floating typeclass.+As of now it contains arbitrary precision implementations for pi (based on Chudnovskis algorithm), sqrt and nthroot (based on Newtons classic algorithm).+All trigonometric functions, log and exp are still missing.+All code contributions are most welcome!+Here are some working examples:++```haskell+λ> r = sqrt (BigDecimal 2 0)+λ> toString r+"1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727"+λ> r^2*pi+BigDecimal 6283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234135488875159962758271904785109490094314219117662951460673928547017151357805018682925970564827587058974690236729643325013696514697383143361638452329945607739055327681644609147889519349178329780951524191191 300+λ> toString it+"6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234135488875159962758271904785109490094314219117662951460673928547017151357805018682925970564827587058974690236729643325013696514697383143361638452329945607739055327681644609147889519349178329780951524191191"++λ> sqr 2 (halfUp 50)+BigDecimal 141421356237309504880168872420969807856967187537695 50+λ> sqr 2 (halfUp 500)+BigDecimal 141421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147010955997160597027453459686201472851741864088919860955232923048430871432145083976260362799525140798968725339654633180882964062061525835239505474575028775996172983557522033753185701135437460340849884716038689997069900481503054402779031645424782306849293691862158057846311159666871301301561856898723724 500++```+
+ src/Data/BigDecimal.hs view
@@ -0,0 +1,214 @@+{- | This module defines the type 'BigDecimal' which provides a representation of arbitrary precision decimal numbers.+ 'BigDecimal' is a native Haskell implementation based on arbitrary sized 'Integer' values.+ The implementation was inspired by Java BigDecimals.++ BigDecimal instantiates the typeclasses 'Num', 'Fractional' and 'Real'. It is thus possible to use all common+ operators like '+', '-', '*', '/', '^' on them.++ Here are a few examples from an interactive GHCI session:++ > λ> a = BigDecimal 144 2+ > λ> toString a+ > "1.44"+ > λ> b = sqrt a+ > λ> toString b+ > "1.2"+ > λ> b * b+ > BigDecimal 144 2+ > λ> b * b * b+ > BigDecimal 1728 3+ > λ> b^2+ > BigDecimal 144 2+ > λ> c = fromString "123.4567890"+ > λ> c+ > BigDecimal 1234567890 7+ > λ> a / c+ > BigDecimal 1166400010614240096589584878965222398584 41+ > λ> roundBD it (halfUp 10)+ > BigDecimal 116640001 10+ > λ> divide (a, c) $ halfUp 20+ > BigDecimal 1166400010614240097 20++-}+module Data.BigDecimal+ ( BigDecimal (..)+ , RoundingMode (..)+ , MathContext+ , getScale+ , getValue+ , precision+ , trim+ , nf+ , divide+ , roundBD+ , fromRatio+ , halfUp+ , fromString+ , matchScales+ , toString+ )+where++import Data.List (find, elemIndex)+import Data.Maybe (fromMaybe)+import GHC.Real ((%), Ratio ((:%)))++-- | RoundingMode defines how to handle loss of precision in divisions or explicit rounding.+data RoundingMode+ = UP -- ^ Rounding mode to round away from zero.+ | DOWN -- ^ Rounding mode to round towards zero.+ | CEILING -- ^ Rounding mode to round towards positive infinity.+ | FLOOR -- ^ Rounding mode to round towards negative infinity.+ | HALF_UP -- ^ Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.+ | HALF_DOWN -- ^ Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.+ | HALF_EVEN -- ^ Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.+ | PRECISE -- ^ Rounding mode to assert that the requested operation has an exact result, hence no rounding is applied.++{-| BigDecimal is represented by an unscaled Integer value plus a second Integer value that defines the scale+ E.g.: (BigDecimal 1234 2) represents the decimal value 12.34.++-}+data BigDecimal =+ -- | creates a BigDecimal value from an unscaled 'Integer' value and a scale, given as a positive 'Integer'.+ -- Example: (BigDecimal 1234 2) creates the value 12.34+ BigDecimal Integer Integer+ deriving (Show, Read)++-- | gets the scale part of a BigDecimal+getScale :: BigDecimal -> Integer+getScale (BigDecimal _ s) = s++-- | get the unscaled value of a BigDecimal+getValue :: BigDecimal -> Integer+getValue (BigDecimal v _) = v++-- | A MathContext is interpreted by divisions and rounding operations to specify the expected loss of precision and the rounding behaviour.+-- MathContext is a pair of a 'RoundingMode' and a target precision of type 'Maybe' 'Integer'. The precision defines the number of digits after the decimal point.+-- If 'Nothing' is given as precision all decimal digits are to be preserved, that is precision is not limited.+type MathContext = (RoundingMode, Maybe Integer)++instance Num BigDecimal where+ a + b = plus (a, b)+ a * b = mul (a, b)+ abs (BigDecimal v s) = BigDecimal (abs v) s+ signum (BigDecimal v _) = BigDecimal (signum v) 0+ fromInteger i = BigDecimal i 0+ negate (BigDecimal v s) = BigDecimal (-v) s++instance Eq BigDecimal where+ a == b =+ let (BigDecimal valA _, BigDecimal valB _) = matchScales (a, b)+ in valA == valB++instance Fractional BigDecimal where+ -- default division rounds up and does not limit precision+ a / b = nf $ divide (matchScales (a, b)) (HALF_UP, Nothing)+ fromRational ratio@(x :% y) = fromRatio ratio (HALF_UP, Nothing)++-- | creates a BigDecimal from a 'Rational' value. 'MathContext' defines precision and rounding mode.+fromRatio :: Rational -> MathContext -> BigDecimal+fromRatio (x :% y) = divide (fromInteger x, fromInteger y)++instance Real BigDecimal where+ toRational (BigDecimal val scale) = toRational val * 10^^(-scale)++instance Ord BigDecimal where+ compare a b =+ let (BigDecimal valA _, BigDecimal valB _) = matchScales (a, b)+ in compare valA valB++-- | add two BigDecimals+plus :: (BigDecimal, BigDecimal) -> BigDecimal+plus (a@(BigDecimal valA scaleA), b@(BigDecimal valB scaleB))+ | scaleA == scaleB = BigDecimal (valA + valB) scaleA+ | otherwise = plus $ matchScales (a,b)++-- | multiply two BigDecimals+mul :: (BigDecimal, BigDecimal) -> BigDecimal+mul (BigDecimal valA scaleA, BigDecimal valB scaleB) = BigDecimal (valA * valB) (scaleA + scaleB)++-- | divide two BigDecimals and applies the 'MathContext' (i.e. a tuple of 'RoundingMode' and the specified precision) for rounding.+divide :: (BigDecimal, BigDecimal) -- ^ the tuple of dividend and divisor. I.e. (dividend, divisor)+ -> MathContext -- ^ 'MathContext' (i.e. a tuple of 'RoundingMode' and the specified precision) defines the rounding behaviour.+ -- if 'Nothing' if given as precision the maximum possible precision is used.+ -> BigDecimal -- ^ the resulting BigDecimal+divide (a, b) (rMode, prefScale) =+ let (BigDecimal numA _, BigDecimal numB _) = matchScales (a, b)+ maxPrecision = fromMaybe (precision a + round (fromInteger (precision b) * 10 / 3)) prefScale+ in trim maxPrecision (BigDecimal (divUsing rMode (numA * (10 :: Integer) ^ maxPrecision) numB) maxPrecision)++-- | divide two correctly scaled Integers and apply the RoundingMode+divUsing :: RoundingMode -> Integer -> Integer -> Integer+divUsing rounding a b =+ let (quot, rem) = quotRem a b+ delta = (10 * abs rem `div` abs b) - 5+ in case rounding of+ PRECISE -> if rem == 0 then quot else error "non-terminating decimal expansion"+ UP -> if abs rem > 0 then quot + signum quot else quot+ CEILING -> if abs rem > 0 && quot >= 0 then quot + 1 else quot+ HALF_UP -> if delta >= 0 then quot + signum quot else quot+ HALF_DOWN -> if delta <= 0 then quot else quot + signum quot+ DOWN -> quot+ FLOOR -> if quot >= 0 then quot else quot - 1+ HALF_EVEN+ | delta > 0 -> quot + signum quot+ | delta == 0 && odd quot -> quot + signum quot+ | otherwise -> quot++-- | round a BigDecimal to 'n' digits applying the 'MathContext' 'mc'+roundBD :: BigDecimal -> MathContext -> BigDecimal+roundBD bd@(BigDecimal val scale) mc@(rMode, Just n)+ | n < 0 || n >= scale = bd+ | otherwise = BigDecimal (divUsing rMode val (10 ^ (scale-n))) n++-- | match the scales of a tuple of BigDecimals+matchScales :: (BigDecimal, BigDecimal) -> (BigDecimal, BigDecimal)+matchScales (a@(BigDecimal integerA scaleA), b@(BigDecimal integerB scaleB))+ | scaleA < scaleB = (BigDecimal (integerA * 10 ^ (scaleB - scaleA)) scaleB, b)+ | scaleA > scaleB = (a, BigDecimal (integerB * 10 ^ (scaleA - scaleB)) scaleA)+ | otherwise = (a, b)++-- | returns the number of digits of an Integer+precision :: BigDecimal -> Integer+precision 0 = 1+precision (BigDecimal val _) = 1 + floor (logBase 10 $ abs $ fromInteger val)++-- | removes trailing zeros from a BigDecimals intValue by decreasing the scale+trim :: Integer -> BigDecimal -> BigDecimal+trim prefScale bd@(BigDecimal val scale) =+ let (v, r) = quotRem val 10+ in if r == 0 && 0 <= prefScale && prefScale < scale+ then trim prefScale $ BigDecimal v (scale - 1)+ else bd++-- | computes the normal form of a BigDecimal+nf :: BigDecimal -> BigDecimal+nf = trim 0++-- | read a BigDecimal from a human readable decimal notation.+-- e.g. @ fromString "3.14" @ yields 'BigDecimal 314 2'+fromString :: String -> BigDecimal+fromString s =+ let maybeIndex = elemIndex '.' s+ intValue = read (filter (/= '.') s) :: Integer+ in case maybeIndex of+ Nothing -> BigDecimal intValue 0+ Just i -> BigDecimal intValue $ toInteger (length s - i - 1)++-- | returns a readable String representation of a BigDecimal+-- e.g. @ toString (BigDecimal 314 2) @ yields "3.14"+toString :: BigDecimal -> String+toString bd@(BigDecimal intValue scale) =+ let s = show $ abs intValue+ filled =+ if fromInteger scale >= length s+ then replicate (1 + fromInteger scale - length s) '0' ++ s+ else s+ splitPos = length filled - fromInteger scale+ (ints, decimals) = splitAt splitPos filled+ sign = if intValue < 0 then "-" else ""+ in sign ++ if not (null decimals) then ints ++ "." ++ decimals else ints++-- | construct a 'MathContext' for rounding 'HALF_UP' with 'scale' decimal digits+halfUp :: Integer -> MathContext+halfUp scale = (HALF_UP, Just scale)
+ src/Data/BigFloating.hs view
@@ -0,0 +1,100 @@+module Data.BigFloating+ ( piChudnovsky+ , sqr+ , nthRoot+ )+where++import Data.BigDecimal+import Data.List (find)+import Data.Maybe (fromMaybe)+import GHC.Real ((%), Ratio ((:%)))++-- I'm giving some implementation ideas for approximisations for functions on transcendental numbers.+-- The rest is left as an exercise to the interested reader ;-)+instance Floating BigDecimal where+ pi = piChudnovsky defaultMC+ exp = undefined -- e^x+ log = undefined+ sin = undefined+ cos = undefined+ asin = undefined+ acos = undefined+ atan = undefined+ sinh = undefined+ cosh = undefined+ asinh = undefined+ acosh = undefined+ atanh = undefined++-- not required for minimal implementation+ sqrt x = sqr x defaultMC+ x ** y = nthRoot (x^b) n defaultMC+ where+ (b :% n) = toRational y++defaultMC = (DOWN, Just 100)++-- | computes the square root of any non-negative BigDecimal, rounding and precision defined by MathContext.+-- We are using Newton's algorithm.+sqr :: BigDecimal -> MathContext -> BigDecimal+sqr x mc+ | x < 0 = error "can't determine the square root of negative numbers"+ | x == 0 = 0+ | otherwise = fst $ fromMaybe (error "did not find a sqrt") $ refine x 1 mc+ where+ refine x initial mc@(_, Just scale) = find withinPrecision $ iterate nextGuess (initial, 0)+ where+ withinPrecision (guess, count) = abs (guess^2 - x) < BigDecimal 10 scale || count > 10 * scale * precision x+ nextGuess (guess, count) = (nf $ divide (guess + divide (x, guess) mc, 2) mc, count+1)++nthRoot :: BigDecimal -> Integer -> MathContext -> BigDecimal+nthRoot x n mc@(r,Just s)+ | x < 0 && even n = error "can't determine even roots of negative numbers"+ | x < 0 && odd n = - nthRoot x (-n) mc+ | x == 0 = 0+ | otherwise = roundBD (fst (fromMaybe (error "did not find a sqrt") $ refine x 1 (r, Just (s+4)))) mc+ where+ refine x initial mc@(_, Just scale) = find withinPrecision $ iterate nextGuess (initial, 0)+ where+ withinPrecision (guess, count) = abs (guess^n - x) < BigDecimal (n*10) scale || count > 10 * scale * precision x+ nextGuess (guess, count) =+ (nf $ divide ((guess * BigDecimal (n-1) 0) + divide (x, guess^(n-1)) mc, BigDecimal n 0) mc, count+1)+++-- | Compute pi using rounding mode and scale of the specified MathContext+-- Sources: https://wiki.haskell.org/Integers_too_big_for_floats & https://github.com/eobermuhlner/big-math+piChudnovsky :: MathContext -> BigDecimal+piChudnovsky mc@(rMode, Just scale) = divide (1, 12 * divide (fromRatio s mc,f) mc') mc+ where+ mc' = (rMode, Just $ scale + 3) -- increase precision to avoid propagation of rounding errors+ steps = 1 + div scale 14 -- taken from github.com/eobermuhlner/big-math+ s = sum [chudnovsky n | n <- [0..steps]] :: Rational+ f = sqr (fromInteger c^3) mc -- Common factor in the sum++ -- k-th term of the Chudnovsky series+ chudnovsky :: Integer -> Rational+ chudnovsky k+ | even k = quot+ | otherwise = -quot+ where+ quot = num % den+ num = facDiv (6 * k) (3 * k) * (a + b * k)+ den = fac k ^ 3 * (c ^ (3 * k))++ -- Compute n!+ fac :: (Enum a, Num a) => a -> a+ fac n = product [1..n]++ -- Compute n! / m! efficiently+ facDiv :: Integer -> Integer -> Integer+ facDiv n m+ | n > m = product [n, n - 1 .. m + 1]+ | n == m = 1+ | otherwise = facDiv m n++ a = 13591409+ b = 545140134+ c = 640320++
+ test/Data/BigDecimalSpec.hs view
@@ -0,0 +1,315 @@+module Data.BigDecimalSpec+ (main, spec)+where++import Control.Exception (evaluate)+import Data.BigDecimal+import GHC.Real (Ratio ((:%)))+import Test.Hspec hiding (it)+import Data.TestUtils (it) -- I'm redefining it to use 1000 examples+import Test.Hspec.QuickCheck (modifyMaxSize, modifyMaxSuccess)+import Test.QuickCheck++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "toBD" $ do+ it "reads BigDecimals from strings" $+ fromString "-145.123" `shouldBe` BigDecimal (-145123) 3+ it "is inverse of toString" $+ property $ \bd -> (fromString . toString) bd === (bd :: BigDecimal)++ describe "toString" $ do+ it "converts BigDecimals to string" $+ toString (BigDecimal (-145123) 3) `shouldBe` "-145.123"+ it "adds leading 0s if required" $+ toString (BigDecimal (-14) 10) `shouldBe` "-0.0000000014"+ it "can handle integer values" $+ toString 10 `shouldBe` "10"+ it "is inverse of toBD" $+ property $ \bd -> (toString . fromString . toString) bd === toString (bd :: BigDecimal)++ describe "read" $ do+ it "reads BigDecimals from strings in constructor notation" $+ read "BigDecimal 76878 5" `shouldBe` BigDecimal 76878 5+ it "is inverse of show" $+ property $ \bd -> (read . show) bd === (bd :: BigDecimal)++ describe "show" $ do+ it "converts BigDecimals to strings in constructor notation" $+ show (BigDecimal 76878 5) `shouldBe` "BigDecimal 76878 5"+ it "is inverse of read" $+ property $ \bd -> (read . show) bd === (bd :: BigDecimal)++ describe "(+)" $ do+ it "adds two BigDecimals" $+ BigDecimal 73 1 + BigDecimal 270 2 `shouldBe` BigDecimal 1000 2+ modifyMaxSuccess (const 1000) $ it "has 0 as neutral element" $+ property $ \bd -> bd + 0 === (bd :: BigDecimal)+ it "adds x to (-x) yielding 0" $+ property $ \bd -> bd + (-bd) === (0 :: BigDecimal)+ it "uses the max scale of the summands" $+ property $ \ai as bi bs -> max as bs === getScale (BigDecimal ai as + BigDecimal bi bs)+ it "uses Integer addition when summands have same scale" $+ property $ \ai bi scale -> ai + bi === getValue (BigDecimal ai scale + BigDecimal bi scale)+ it "matches values when scaling" $+ property $ \ai bi scale -> getValue (BigDecimal ai scale + BigDecimal bi (scale+1)) === 10*ai + bi++ describe "(*)" $ do+ it "multiplies BigDecimals" $+ BigDecimal 12 1 * BigDecimal 12 2 `shouldBe` BigDecimal 144 3+ it "has 1 as neutral element" $+ property $ \bd -> bd * 1 === (bd :: BigDecimal)+ it "has 0 as zero element" $+ property $ \bd -> bd * 0 === (0 :: BigDecimal)+ it "Uses Integer multiplication" $+ property $ \ai as bi -> BigDecimal ai as * BigDecimal bi 0 === BigDecimal (ai*bi) as+ it "adds the scales of the multiplicands" $+ property $ \ai as bi bs -> BigDecimal ai as * BigDecimal bi bs === BigDecimal (ai*bi) (as+bs)++ describe "abs" $ do+ it "determines the absolute value of a BigDecimal" $+ abs (BigDecimal (-12) 4) `shouldBe` BigDecimal 12 4+ it "is idempotent" $+ property $ \bd -> (abs . abs) bd === (abs bd :: BigDecimal)+ it "is based on abs for Integers" $+ property $ \ai as -> abs (BigDecimal ai as) === BigDecimal (abs ai) as+ it "negates for input < 0" $+ property $ \bd -> abs bd === if getValue bd < 0 then negate bd else bd++ describe "signum" $ do+ it "determines the signature a BigDecimal" $+ signum (BigDecimal (-12) 4) `shouldBe` -1+ it "returns 1 if input > 0, zero if input == 0 and -1 if input < 0" $+ property $ \ai as -> signum (BigDecimal ai as) === if ai > 0 then 1 else if ai == 0 then 0 else -1+ it "is based on signum for Integers" $+ property $ \ai as -> signum (BigDecimal ai as) === BigDecimal (signum ai) 0++ describe "fromInteger" $ do+ it "constructs a BigDecimal from an Integer" $+ 1234 `shouldBe` BigDecimal 1234 0+ it "works for any Integer" $+ property $ \i -> fromInteger i === BigDecimal i 0++ describe "negate" $ do+ it "negates a BigDecimal" $+ negate (BigDecimal 1234 1) `shouldBe` -BigDecimal 1234 1+ it "works for any BigDecimal" $+ property $ \bd -> negate bd === (-bd :: BigDecimal)+ it "is the same as *(-1)" $+ property $ \bd -> negate bd === (-1 * bd :: BigDecimal)+ it "is its own inverse" $+ property $ \bd -> negate (negate bd) === (bd :: BigDecimal)++ describe "(/)" $ do+ it "divides two BigDecimals" $+ BigDecimal 16 1 / BigDecimal 4 1 `shouldBe` BigDecimal 4 0+ it "yields x for x/1 for any x" $+ property $ \x -> x/1 === (x :: BigDecimal)+ it "yields 1 for x/x any non-zero x" $+ property $ \x -> if x /= (0 :: BigDecimal) then x / x === 1 else 1===1+ it "throws an Arithmetic exception when dividing by 0" $+ property $ \bd -> evaluate (bd / 0 :: BigDecimal) `shouldThrow` anyArithException+ it "yields y for (x*y)/x for any nonzero x" $+ property $ \x y -> y === if x == (0 :: BigDecimal) then y else (x*y)/x+ it "rounds up if next decimal would be > 5" $+ 6 / 9 `shouldBe` fromString "0.6667"+ it "rounds up if next decimal would be = 5" $+ 5 / 9 `shouldBe` fromString "0.5556"+ it "rounds down if next decimal would be < 5" $+ 4 / 9 `shouldBe` fromString "0.4444"++ describe "fromRational" $ do+ it "constructs a BigDecimal from a Ratio" $+ fromRational (1 :% 32) `shouldBe` 1 / BigDecimal 32 0+ it "works for any non-zero divisors" $+ property $ \x y -> if y == 0 then 1 ===1 else fromRational (x :% y) === BigDecimal x 0 / BigDecimal y 0++ describe "toRational" $ do+ it "converts a BigDecimal to a Ratio" $+ toRational (1 / BigDecimal 32 0) `shouldBe` (1 :% 32)+ it "is inverse to fromRational" $+ property $ \x -> (x::BigDecimal) === fromRational (toRational x)++ describe "divide +/+" $ do+ -- checking expresions >= 0+ it "divides BigDecimals applying RoundingMode and precision" $+ divide (2, 3) (HALF_UP, Just 9) `shouldBe` fromString "0.666666667"+ it "always rounds down when using DOWN" $+ divide (2, 3) (DOWN, Just 9) `shouldBe` fromString "0.666666666"+ it "always rounds down when using FLOOR" $+ divide (2, 3) (FLOOR, Just 9) `shouldBe` fromString "0.666666666"+ it "rounds up when using UP when there is a remainder" $+ divide (1, 9) (UP, Just 3) `shouldBe` fromString "0.112"+ it "does not round when there is no remainder when using UP" $+ divide (14, 100) (UP, Just 2) `shouldBe` fromString "0.14"+ it "rounds up when using UP when there is a remainder" $+ divide (1, 9) (CEILING, Just 3) `shouldBe` fromString "0.112"+ it "does not round when there is no remainder when using UP" $+ divide (14, 100) (CEILING, Just 2) `shouldBe` fromString "0.14"+ it "rounds down if next decimal would be <= 5 when using HALF_DOWN" $+ divide (5, 9) (HALF_DOWN, Just 4) `shouldBe` fromString "0.5555"+ it "rounds up if next decimal would be > 5 when using HALF_DOWN" $+ divide (2, 3) (HALF_DOWN, Just 4) `shouldBe` fromString "0.6667"+ it "rounds up if next decimal would be >= 5 when using HALF_UP" $+ divide (5, 9) (HALF_UP, Just 4) `shouldBe` fromString "0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (5, 9) (HALF_EVEN, Just 4) `shouldBe` fromString "0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (1, 8) (HALF_EVEN, Just 2) `shouldBe` fromString "0.12"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (15, 100) (HALF_EVEN, Just 1) `shouldBe` fromString "0.2"+ it "rounds up if next decimal would be > 5 when using HALF_EVEN" $+ divide (2, 3) (HALF_EVEN, Just 4) `shouldBe` fromString "0.6667"+ it "throws an exception when PRECISE is used and a non-terminating decimal expansion is detected" $+ evaluate (divide (5, 9) (PRECISE, Nothing)) `shouldThrow` anyException+ it "gives a precise value when using PRECISE and no max precision" $+ divide (1, 32) (PRECISE, Nothing) `shouldBe` fromString "0.03125"+ it "gives a precise value when using PRECISE and a sufficient precision" $+ divide (1, 32) (PRECISE, Just 5) `shouldBe` fromString "0.03125"+ it "gives a precise value when using PRECISE and a to small precision" $+ evaluate (divide (1, 32) (PRECISE, Just 4)) `shouldThrow` anyException++ describe "divide -/+" $ do+ -- checking dividend < 0+ it "divides BigDecimals applying RoundingMode and precision" $+ divide (-2, 3) (HALF_UP, Just 9) `shouldBe` fromString "-0.666666667"+ it "always rounds down when using DOWN" $+ divide (-2, 3) (DOWN, Just 9) `shouldBe` fromString "-0.666666666"+ it "always rounds towards -INF when using FLOOR" $+ divide (-2, 3) (FLOOR, Just 9) `shouldBe` fromString "-0.666666667"+ it "rounds up when using UP when there is a remainder" $+ divide (-1, 9) (UP, Just 3) `shouldBe` fromString "-0.112"+ it "does not round when there is no remainder when using UP" $+ divide (-14, 100) (UP, Just 2) `shouldBe` fromString "-0.14"+ it "rounds towards +INF when using CEILING when there is a remainder" $+ divide (-1, 9) (CEILING, Just 3) `shouldBe` fromString "-0.111"+ it "does not round when there is no remainder when using UP" $+ divide (-14, 100) (CEILING, Just 2) `shouldBe` fromString "-0.14"+ it "rounds down if next decimal would be <= 5 when using HALF_DOWN" $+ divide (-5, 9) (HALF_DOWN, Just 4) `shouldBe` fromString "-0.5555"+ it "rounds up if next decimal would be > 5 when using HALF_DOWN" $+ divide (-2, 3) (HALF_DOWN, Just 4) `shouldBe` fromString "-0.6667"+ it "rounds up if next decimal would be >= 5 when using HALF_UP" $+ divide (-5, 9) (HALF_UP, Just 4) `shouldBe` fromString "-0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-5, 9) (HALF_EVEN, Just 4) `shouldBe` fromString "-0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-1, 8) (HALF_EVEN, Just 2) `shouldBe` fromString "-0.12"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-15, 100) (HALF_EVEN, Just 1) `shouldBe` fromString "-0.2"+ it "rounds up if next decimal would be > 5 when using HALF_EVEN" $+ divide (-2, 3) (HALF_EVEN, Just 4) `shouldBe` fromString "-0.6667"+ it "throws an exception when PRECISE is used and a non-terminating decimal expansion is detected" $+ evaluate (divide (-5, 9) (PRECISE, Nothing)) `shouldThrow` anyException+ it "gives a precise value when using PRECISE and no max precision" $+ divide (-1, 32) (PRECISE, Nothing) `shouldBe` fromString "-0.03125"+ it "gives a precise value when using PRECISE and a sufficient precision" $+ divide (-1, 32) (PRECISE, Just 5) `shouldBe` fromString "-0.03125"+ it "gives a precise value when using PRECISE and a to small precision" $+ evaluate (divide (-1, 32) (PRECISE, Just 4)) `shouldThrow` anyException++ describe "divide +/-" $ do+ -- checking divisor < 0+ it "divides BigDecimals applying RoundingMode and precision" $+ divide (2, -3) (HALF_UP, Just 9) `shouldBe` fromString "-0.666666667"+ it "always rounds down when using DOWN" $+ divide (2, -3) (DOWN, Just 9) `shouldBe` fromString "-0.666666666"+ it "always rounds towards -INF when using FLOOR" $+ divide (2, -3) (FLOOR, Just 9) `shouldBe` fromString "-0.666666667"+ it "rounds up when using UP when there is a remainder" $+ divide (1, -9) (UP, Just 3) `shouldBe` fromString "-0.112"+ it "does not round when there is no remainder when using UP" $+ divide (14, -100) (UP, Just 2) `shouldBe` fromString "-0.14"+ it "rounds towards +INF when using CEILING when there is a remainder" $+ divide (1, -9) (CEILING, Just 3) `shouldBe` fromString "-0.111"+ it "does not round when there is no remainder when using UP" $+ divide (14, -100) (CEILING, Just 2) `shouldBe` fromString "-0.14"+ it "rounds down if next decimal would be <= 5 when using HALF_DOWN" $+ divide (5, -9) (HALF_DOWN, Just 4) `shouldBe` fromString "-0.5555"+ it "rounds up if next decimal would be > 5 when using HALF_DOWN" $+ divide (2, -3) (HALF_DOWN, Just 4) `shouldBe` fromString "-0.6667"+ it "rounds up if next decimal would be >= 5 when using HALF_UP" $+ divide (5, -9) (HALF_UP, Just 4) `shouldBe` fromString "-0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (5, -9) (HALF_EVEN, Just 4) `shouldBe` fromString "-0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (1, -8) (HALF_EVEN, Just 2) `shouldBe` fromString "-0.12"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (15, -100) (HALF_EVEN, Just 1) `shouldBe` fromString "-0.2"+ it "rounds up if next decimal would be > 5 when using HALF_EVEN" $+ divide (2, -3) (HALF_EVEN, Just 4) `shouldBe` fromString "-0.6667"+ it "throws an exception when PRECISE is used and a non-terminating decimal expansion is detected" $+ evaluate (divide (5, -9) (PRECISE, Nothing)) `shouldThrow` anyException+ it "gives a precise value when using PRECISE and no max precision" $+ divide (1, -32) (PRECISE, Nothing) `shouldBe` fromString "-0.03125"+ it "gives a precise value when using PRECISE and a sufficient precision" $+ divide (1, -32) (PRECISE, Just 5) `shouldBe` fromString "-0.03125"+ it "gives a precise value when using PRECISE and a to small precision" $+ evaluate (divide (1, -32) (PRECISE, Just 4)) `shouldThrow` anyException++ describe "divide -/-" $ do+ -- checking dividend and divisor < 0+ it "divides BigDecimals applying RoundingMode and precision" $+ divide (-2, -3) (HALF_UP, Just 9) `shouldBe` fromString "0.666666667"+ it "always rounds down when using DOWN" $+ divide (-2, -3) (DOWN, Just 9) `shouldBe` fromString "0.666666666"+ it "always rounds towards -INF when using FLOOR" $+ divide (-2, -3) (FLOOR, Just 9) `shouldBe` fromString "0.666666666"+ it "rounds up when using UP when there is a remainder" $+ divide (-1, -9) (UP, Just 3) `shouldBe` fromString "0.112"+ it "does not round when there is no remainder when using UP" $+ divide (-14, -100) (UP, Just 2) `shouldBe` fromString "0.14"+ it "rounds towards +INF when using CEILING when there is a remainder" $+ divide (-1, -9) (CEILING, Just 3) `shouldBe` fromString "0.112"+ it "does not round when there is no remainder when using UP" $+ divide (-14, -100) (CEILING, Just 2) `shouldBe` fromString "0.14"+ it "rounds down if next decimal would be <= 5 when using HALF_DOWN" $+ divide (-5, -9) (HALF_DOWN, Just 4) `shouldBe` fromString "0.5555"+ it "rounds up if next decimal would be > 5 when using HALF_DOWN" $+ divide (-2, -3) (HALF_DOWN, Just 4) `shouldBe` fromString "0.6667"+ it "rounds up if next decimal would be >= 5 when using HALF_UP" $+ divide (-5, -9) (HALF_UP, Just 4) `shouldBe` fromString "0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-5, -9) (HALF_EVEN, Just 4) `shouldBe` fromString "0.5556"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-1, -8) (HALF_EVEN, Just 2) `shouldBe` fromString "0.12"+ it "rounds to next even number if next decimal would be == 5 when using HALF_EVEN" $+ divide (-15, -100) (HALF_EVEN, Just 1) `shouldBe` fromString "0.2"+ it "rounds up if next decimal would be > 5 when using HALF_EVEN" $+ divide (-2, -3) (HALF_EVEN, Just 4) `shouldBe` fromString "0.6667"+ it "throws an exception when PRECISE is used and a non-terminating decimal expansion is detected" $+ evaluate (divide (-5, -9) (PRECISE, Nothing)) `shouldThrow` anyException+ it "gives a precise value when using PRECISE and no max precision" $+ divide (-1, -32) (PRECISE, Nothing) `shouldBe` fromString "0.03125"+ it "gives a precise value when using PRECISE and a sufficient precision" $+ divide (-1, -32) (PRECISE, Just 5) `shouldBe` fromString "0.03125"+ it "gives a precise value when using PRECISE and a to small precision" $+ evaluate (divide (-1, -32) (PRECISE, Just 4)) `shouldThrow` anyException+++ describe "shrink" $ do+ it "removes trailing zeros while taking care of the scale" $+ nf (BigDecimal 1000 3) `shouldBe` BigDecimal 1 0+ it "does not eliminate more 0s than requested" $+ trim 2 (BigDecimal 1000 3) `shouldBe` BigDecimal 100 2+ it "does not eliminate more 0s than possible" $+ nf (BigDecimal 1230 3) `shouldBe` BigDecimal 123 2+ it "does not change the value of a BigDecimal" $+ property $ \bd n -> trim n bd === bd++ describe "matchScales" $+ it "adjusts a pair of BigDecimals to use the same scale" $+ property $ \x y -> let (x', y') = matchScales (x,y) in getScale x' === getScale y'++ describe "roundBD" $ do+ it "rounds a BigDecimal " $+ roundBD (BigDecimal 123456 3) (halfUp 2) `shouldBe` BigDecimal 12346 2+ it "ignores negative scales in MathContext" $+ roundBD (BigDecimal 123456 3) (halfUp (-2)) `shouldBe` BigDecimal 123456 3+ it "ignores MathContext with scale higher than in input value" $+ roundBD (BigDecimal 123456 3) (halfUp 10) `shouldBe` BigDecimal 123456 3
+ test/Data/BigFloatingSpec.hs view
@@ -0,0 +1,45 @@+module Data.BigFloatingSpec+ (main, spec)+where++import Control.Exception (evaluate)+import Data.BigDecimal+import Data.BigFloating+import GHC.Real (Ratio ((:%)))+import Test.Hspec hiding (it)+import Data.TestUtils (it) -- I'm redefining it to use 1000 examples+import Test.Hspec.QuickCheck (modifyMaxSize, modifyMaxSuccess)+import Test.QuickCheck+++-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ -- mathematical functions on BigDecimals+ describe "sqr" $ do+ it "computes the square root of any non-negative BigDecimal" $+ property $ \x scale -> let (x', r) = (abs x, sqr x' $ halfUp scale) in abs (r*r - x') < BigDecimal 1000 scale+ it "throws an exception if applied to a negative number" $+ evaluate (sqr (-16) $ halfUp 2) `shouldThrow` anyException++ -- mathematical functions on BigDecimals+ describe "nthRoot" $ do+ it "computes the nth root of any non-negative BigDecimal" $+ property $ \x n -> let (x', n', r) = (1+ abs x, 1+abs n, nthRoot x' n' (halfUp 10)) in abs (r^n' - x') < BigDecimal (n'*10000) 10+ it "throws an exception if trying to get even root of a negative number" $+ evaluate (nthRoot (-16) 4 $ halfUp 2) `shouldThrow` anyException+ --it "computes odd roots of any negative BigDecimal" $+ -- property $ \x n -> let (x', n', r) = ((-1)- abs x, if even n then 1 + abs n else abs n, nthRoot x' n' (halfUp 10)) in abs (r^n' - x') < BigDecimal (n'*10000) 10+++ describe "pi" $+ it "computes pi with a default precision of 100 decimal digits" $+ pi `shouldBe` fromString "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"++ describe "piChudnovsky" $+ it "computes pi with arbitrary precision (demonstrating it with 1000 digits)" $+ piChudnovsky (FLOOR, Just 1000) `shouldBe` fromString "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989"
+ test/Data/TestUtils.hs view
@@ -0,0 +1,19 @@+module Data.TestUtils where++import Test.Hspec hiding (it)+import qualified Test.Hspec as HS (it)+import Test.Hspec.QuickCheck (modifyMaxSize, modifyMaxSuccess)+import Test.QuickCheck hiding (shrink)+import Data.BigDecimal++-- redefine it to use a sample with 1000 elements+it :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)+it label action = modifyMaxSuccess (const 1000) $ HS.it label action++-- arbitrary BigDecimals can be constructed using any Integer as unscaled value+-- and any non-negative Integer as scale+instance Arbitrary BigDecimal where+ arbitrary = do+ unscaledValue <- arbitrary+ NonNegative scale <- arbitrary+ return $ BigDecimal unscaledValue scale
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-} -- this compiler pragma allows GHC to automatically discover all Hspec Test Specs.