├── package.json ├── .gitignore ├── packages.dhall ├── spago.dhall ├── .github └── workflows │ └── ci.yml ├── bower.json ├── LICENSE ├── README.md ├── src └── Data │ ├── Decimal.js │ └── Decimal.purs └── test └── Main.purs /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "decimal.js": "10.3.1" 4 | }, 5 | "type": "module" 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psc* 6 | /.psa* 7 | /.spago/ 8 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220503/packages.dhall 3 | sha256:847d49acea4803c3d42ef46114053561e91840e35ede29f0a8014d09d47cd8df 4 | 5 | in upstream 6 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "decimals" 2 | , dependencies = 3 | [ "assert" 4 | , "console" 5 | , "effect" 6 | , "foldable-traversable" 7 | , "integers" 8 | , "maybe" 9 | , "numbers" 10 | , "prelude" 11 | , "quickcheck" 12 | , "tuples" 13 | ] 14 | , packages = ./packages.dhall 15 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: purescript-contrib/setup-purescript@main 18 | 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: "14" 22 | 23 | - name: Install dependencies 24 | run: | 25 | npm install 26 | spago install 27 | 28 | - name: Build source 29 | run: | 30 | spago build 31 | 32 | - name: Run tests 33 | run: | 34 | spago test 35 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-decimals", 3 | "description": "Arbitrary precision numbers", 4 | "license": "MIT", 5 | "homepage": "https://github.com/sharkdp/purescript-decimals", 6 | "authors": [ 7 | "David Peter " 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/sharkdp/purescript-decimals.git" 12 | }, 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "output" 18 | ], 19 | "dependencies": { 20 | "purescript-maybe": "^6.0.0" 21 | }, 22 | "devDependencies": { 23 | "purescript-psci-support": "^6.0.0", 24 | "purescript-assert": "^6.0.0", 25 | "purescript-quickcheck": "^8.0.0", 26 | "purescript-numbers": "^9.0.0", 27 | "purescript-foldable-traversable": "^6.0.0", 28 | "purescript-tuples": "^7.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Peter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-decimals 2 | 3 | A library for calculations with arbitrary precision numbers. This is a simple 4 | wrapper around [decimal.js](http://mikemcl.github.io/decimal.js). 5 | 6 | 7 | ## Module documentation 8 | 9 | - [Published on Pursuit](http://pursuit.purescript.org/packages/purescript-decimals/) 10 | 11 | ## Example 12 | 13 | ```purescript 14 | > let x = fromMaybe zero (fromString "12345.67898765432123456789") 15 | > let y = fromInt 100000 16 | > x + y 17 | (fromString "112345.67898765432123456789") 18 | 19 | > toString (x `pow` y) 20 | "3.1479315197661937753e+409151" 21 | 22 | > let two = fromNumber 2.0 23 | > sin (pi / two) 24 | (fromString "1") 25 | ``` 26 | 27 | ## Installation and usage 28 | You can install this package via Bower. You will also need 29 | [decimal.js](http://mikemcl.github.io/decimal.js), which can be installed 30 | via `npm`: 31 | ``` 32 | bower install purescript-decimals 33 | npm install 34 | ``` 35 | For the browser, remember to bundle `decimal.js` with your code. 36 | 37 | Check the `package.json` file for the supported versions of `decimal.json`. 38 | 39 | ## Development 40 | ``` 41 | bower install 42 | npm install 43 | ``` 44 | Then, use `pulp` to build and run tests. 45 | -------------------------------------------------------------------------------- /src/Data/Decimal.js: -------------------------------------------------------------------------------- 1 | import Decimal from "decimal.js"; 2 | 3 | Decimal.set({ precision: 30 }); 4 | Decimal.set({ modulo: Decimal.EUCLID }); 5 | 6 | export function fromNumber(x) { 7 | return new Decimal(x); 8 | } 9 | 10 | export { fromNumber as fromInt }; 11 | 12 | export function fromStringImpl(nothing) { 13 | return function(just) { 14 | return function(str) { 15 | try { 16 | return just(new Decimal(str)); 17 | } 18 | catch (e) { 19 | return nothing; 20 | } 21 | }; 22 | }; 23 | } 24 | 25 | export function toNumber(x) { 26 | return x.toNumber(); 27 | } 28 | 29 | export function toString(x) { 30 | return x.toString(); 31 | } 32 | 33 | export function toPrecision(d) { 34 | return function (x) { 35 | return x.toPrecision(d); 36 | }; 37 | } 38 | 39 | export function toFixed(d) { 40 | return function(x) { 41 | return x.toFixed(d); 42 | } 43 | } 44 | 45 | export function isInteger(x) { 46 | return x.isInteger(); 47 | } 48 | 49 | export function isFinite(x) { 50 | return x.isFinite(); 51 | } 52 | 53 | export function toSignificantDigits(d) { 54 | return function (x) { 55 | return x.toSignificantDigits(d); 56 | }; 57 | } 58 | 59 | export function toDecimalPlaces(d) { 60 | return function (x) { 61 | return x.toDecimalPlaces(d); 62 | } 63 | } 64 | 65 | export function dAdd(x) { 66 | return function(y) { 67 | return x.add(y); 68 | }; 69 | } 70 | 71 | export function modulo(x) { 72 | return function(y) { 73 | return x.mod(y); 74 | }; 75 | } 76 | 77 | export function dMul(x) { 78 | return function(y) { 79 | return x.mul(y); 80 | }; 81 | } 82 | 83 | export function dSub(x) { 84 | return function(y) { 85 | return x.minus(y); 86 | }; 87 | } 88 | 89 | export function dDiv(x) { 90 | return function(y) { 91 | return x.div(y); 92 | }; 93 | } 94 | 95 | export function dEquals(x) { 96 | return function(y) { 97 | return x.equals(y); 98 | }; 99 | } 100 | 101 | export function dCompare(x) { 102 | return function(y) { 103 | return x.cmp(y); 104 | }; 105 | } 106 | 107 | export function clamp(min) { 108 | return function(max) { 109 | return function(x) { 110 | return x.clamp(min, max); 111 | }; 112 | } 113 | }; 114 | 115 | export function abs(x) { 116 | return x.abs(); 117 | } 118 | 119 | export function pow(x) { 120 | return function(y) { 121 | return x.pow(y); 122 | }; 123 | } 124 | 125 | export function exp(x) { 126 | return x.exp(); 127 | } 128 | 129 | export function acos(x) { 130 | return x.acos(); 131 | } 132 | 133 | export function acosh(x) { 134 | return x.acosh(); 135 | } 136 | 137 | export function asin(x) { 138 | return x.asin(); 139 | } 140 | 141 | export function asinh(x) { 142 | return x.asinh(); 143 | } 144 | 145 | export function atan(x) { 146 | return x.atan(); 147 | } 148 | 149 | export function atanh(x) { 150 | return x.atanh(); 151 | } 152 | 153 | export function atan2(x) { 154 | return function(y) { 155 | return Decimal.atan2(x, y); 156 | }; 157 | } 158 | 159 | export function ceil(x) { 160 | return x.ceil(); 161 | } 162 | 163 | export function cos(x) { 164 | return x.cos(); 165 | } 166 | 167 | export function cosh(x) { 168 | return x.cosh(); 169 | } 170 | 171 | export function floor(x) { 172 | return x.floor(); 173 | } 174 | 175 | export function ln(x) { 176 | return x.ln(); 177 | } 178 | 179 | export function log2(x) { 180 | return Decimal.log2(x); 181 | } 182 | 183 | export function log10(x) { 184 | return Decimal.log10(x); 185 | } 186 | 187 | export function max(x) { 188 | return function(y) { 189 | return Decimal.max(x, y); 190 | }; 191 | } 192 | 193 | export function min(x) { 194 | return function(y) { 195 | return Decimal.min(x, y); 196 | }; 197 | } 198 | 199 | export function round(x) { 200 | return x.round(); 201 | } 202 | 203 | export function truncated(x) { 204 | return x.truncated(); 205 | } 206 | 207 | export function sin(x) { 208 | return x.sin(); 209 | } 210 | 211 | export function sinh(x) { 212 | return x.sinh(); 213 | } 214 | 215 | export function sqrt(x) { 216 | return x.sqrt(); 217 | } 218 | 219 | export function tan(x) { 220 | return x.tan(); 221 | } 222 | 223 | export function tanh(x) { 224 | return x.tanh(); 225 | } 226 | 227 | export var e = Decimal.exp(1.0); 228 | export var pi = new Decimal('3.14159265358979323846264338327950288419716939937510582097494459230781640628620899'); 229 | 230 | var p = [ 231 | 676.5203681218851, 232 | -1259.1392167224028, 233 | 771.32342877765313, 234 | -176.61502916214059, 235 | 12.507343278686905, 236 | -0.13857109526572012, 237 | 9.9843695780195716e-6, 238 | 1.5056327351493116e-7, 239 | ]; 240 | 241 | export function gamma(z) { 242 | var pval, i, x, y, t, zr; 243 | var one = new Decimal(1.0); 244 | if (z < 0.5) { 245 | // Reflection formula 246 | y = Decimal.div( 247 | pi, 248 | Decimal.mul(Decimal.sin(pi.mul(z)), gamma(one.sub(z)))); 249 | } 250 | else { 251 | zr = z.sub(one); 252 | x = new Decimal(0.99999999999980993); 253 | i = 0; 254 | for (i = 0; i < p.length; i++) { 255 | pval = p[i]; 256 | x = x.add(Decimal.div(pval, zr.add(i).add(one))); 257 | } 258 | t = zr.add(p.length).sub(0.5); 259 | y = Decimal.sqrt(pi.mul(2.0)) 260 | .mul(Decimal.pow(t, zr.add(0.5))) 261 | .mul(Decimal.exp(t.neg())) 262 | .mul(x); 263 | } 264 | if (z.isInteger()) { 265 | return y.round(); 266 | } 267 | return y; 268 | } 269 | -------------------------------------------------------------------------------- /src/Data/Decimal.purs: -------------------------------------------------------------------------------- 1 | -- | This module defines a `Decimal` data type for arbitrary precision numbers. 2 | module Data.Decimal 3 | ( Decimal(..) 4 | , fromString 5 | , fromInt 6 | , fromNumber 7 | , toNumber 8 | , toString 9 | , toPrecision 10 | , toFixed 11 | , isFinite 12 | , isInteger 13 | , toSignificantDigits 14 | , toDecimalPlaces 15 | , abs 16 | , acos 17 | , acosh 18 | , acot 19 | , acoth 20 | , acsc 21 | , acsch 22 | , asec 23 | , asech 24 | , asin 25 | , asinh 26 | , atan 27 | , atan2 28 | , atanh 29 | , ceil 30 | , clamp 31 | , cos 32 | , cosh 33 | , cot 34 | , coth 35 | , csc 36 | , csch 37 | , exp 38 | , floor 39 | , ln 40 | , log2 41 | , log10 42 | , max 43 | , min 44 | , modulo 45 | , pow 46 | , round 47 | , sec 48 | , sech 49 | , sin 50 | , sinh 51 | , sqrt 52 | , tan 53 | , tanh 54 | , truncated 55 | , e 56 | , pi 57 | , gamma 58 | , factorial 59 | ) where 60 | 61 | import Prelude 62 | 63 | import Data.Maybe (Maybe(..)) 64 | 65 | -- | An arbitrary precision number. 66 | foreign import data Decimal ∷ Type 67 | 68 | -- | Convert an integer to a Decimal. 69 | foreign import fromInt ∷ Int → Decimal 70 | 71 | -- | Convert a number to a Decimal. 72 | foreign import fromNumber ∷ Number → Decimal 73 | 74 | -- | Converts a Decimal to a Number, possibly resulting in loss of precision. 75 | foreign import toNumber ∷ Decimal → Number 76 | 77 | foreign import fromStringImpl ∷ ∀ a. Maybe a → (a → Maybe a) → String → Maybe Decimal 78 | 79 | -- | Parse a string into a `Decimal`, assuming a decimal representation. Returns 80 | -- | `Nothing` if the parse fails. 81 | -- | 82 | -- | Examples: 83 | -- | ```purescript 84 | -- | fromString "42.001" 85 | -- | fromString "857981209301293808359384092830482" 86 | -- | fromString "1e100" 87 | -- | fromString "0.12301928309128183579487598149533" 88 | -- | ``` 89 | fromString ∷ String → Maybe Decimal 90 | fromString = fromStringImpl Nothing Just 91 | 92 | foreign import dEquals ∷ Decimal → Decimal → Boolean 93 | 94 | instance Eq Decimal where 95 | eq = dEquals 96 | 97 | foreign import dCompare ∷ Decimal → Decimal → Int 98 | 99 | instance Ord Decimal where 100 | compare x y = 101 | case dCompare x y of 102 | 1 → GT 103 | 0 → EQ 104 | _ → LT 105 | 106 | -- | A representation of the `Decimal` as a `String`. 107 | foreign import toString ∷ Decimal → String 108 | 109 | -- | A representation of the `Decimal` as a `String`, rounded to the given 110 | -- | number of significant digits 111 | foreign import toPrecision ∷ Int → Decimal → String 112 | 113 | -- | A representation of the `Decimal` as a `String` in fixed-point notation 114 | -- | rounded to the given number of decimal places. Never returns exponential 115 | -- | notation. Uses the default rounding mode. 116 | foreign import toFixed ∷ Int → Decimal → String 117 | 118 | -- | Returns true if the value of this `Decimal` is a finite number (not 119 | -- | infinite, not `NaN`). 120 | foreign import isFinite ∷ Decimal → Boolean 121 | 122 | -- | Returns true if the value of this `Decimal` is a whole number. 123 | foreign import isInteger ∷ Decimal → Boolean 124 | 125 | instance Show Decimal where 126 | show x = "(fromString \"" <> toString x <> "\")" 127 | 128 | foreign import dAdd ∷ Decimal → Decimal → Decimal 129 | foreign import dMul ∷ Decimal → Decimal → Decimal 130 | 131 | instance Semiring Decimal where 132 | add = dAdd 133 | zero = fromInt 0 134 | mul = dMul 135 | one = fromInt 1 136 | 137 | foreign import dSub ∷ Decimal → Decimal → Decimal 138 | 139 | instance Ring Decimal where 140 | sub = dSub 141 | 142 | foreign import dDiv ∷ Decimal → Decimal → Decimal 143 | 144 | instance CommutativeRing Decimal 145 | 146 | instance EuclideanRing Decimal where 147 | div = dDiv 148 | mod _ _ = zero 149 | degree _ = one 150 | 151 | instance DivisionRing Decimal where 152 | recip = dDiv one 153 | 154 | -- | Round to the given number of significant digits. 155 | foreign import toSignificantDigits ∷ Int → Decimal → Decimal 156 | 157 | -- | Round to the given number of decimals places. 158 | foreign import toDecimalPlaces :: Int -> Decimal -> Decimal 159 | 160 | -- | The absolute value. 161 | foreign import abs ∷ Decimal → Decimal 162 | 163 | -- | Inverse cosine. 164 | foreign import acos ∷ Decimal → Decimal 165 | 166 | -- | Inverse hyperbolic cosine. 167 | foreign import acosh ∷ Decimal → Decimal 168 | 169 | -- | Hyperbolic sine. 170 | foreign import asin ∷ Decimal → Decimal 171 | 172 | -- | Inverse hyperbolic sine. 173 | foreign import asinh ∷ Decimal → Decimal 174 | 175 | -- | Inverse tangent. 176 | foreign import atan ∷ Decimal → Decimal 177 | 178 | -- | Inverse hyperbolic tangent. 179 | foreign import atan2 ∷ Decimal → Decimal → Decimal 180 | 181 | -- | Inverse hyperbolic tangent. 182 | foreign import atanh ∷ Decimal → Decimal 183 | 184 | -- | Rounded to next whole number in the direction of `+inf`. 185 | foreign import ceil ∷ Decimal → Decimal 186 | 187 | -- | Clamp a number to the range delineated by the first two parameters. 188 | foreign import clamp ∷ Decimal → Decimal → Decimal → Decimal 189 | 190 | -- | Cosine. 191 | foreign import cos ∷ Decimal → Decimal 192 | 193 | -- | Hyperbolic cosine. 194 | foreign import cosh ∷ Decimal → Decimal 195 | 196 | -- | Exponential function. 197 | foreign import exp ∷ Decimal → Decimal 198 | 199 | -- | Rounded to next whole number in the direction of `-inf`. 200 | foreign import floor ∷ Decimal → Decimal 201 | 202 | -- | Natural logarithm. 203 | foreign import ln ∷ Decimal → Decimal 204 | 205 | -- | Logarithm with base `2`. 206 | foreign import log2 ∷ Decimal → Decimal 207 | 208 | -- | Logarithm with base `10`. 209 | foreign import log10 ∷ Decimal → Decimal 210 | 211 | -- | The larger of two numbers. 212 | foreign import max ∷ Decimal → Decimal → Decimal 213 | 214 | -- | The smaller of two numbers. 215 | foreign import min ∷ Decimal → Decimal → Decimal 216 | 217 | -- | Modulo operation. 218 | foreign import modulo ∷ Decimal → Decimal → Decimal 219 | 220 | -- | Exponentiation for `Decimal`. 221 | foreign import pow ∷ Decimal → Decimal → Decimal 222 | 223 | -- | Round to the nearest whole number. 224 | foreign import round ∷ Decimal → Decimal 225 | 226 | -- | Sine. 227 | foreign import sin ∷ Decimal → Decimal 228 | 229 | -- | Hyperbolic sine. 230 | foreign import sinh ∷ Decimal → Decimal 231 | 232 | -- | Square root. 233 | foreign import sqrt ∷ Decimal → Decimal 234 | 235 | -- | Tangent. 236 | foreign import tan ∷ Decimal → Decimal 237 | 238 | -- | Hyperbolic tangent. 239 | foreign import tanh ∷ Decimal → Decimal 240 | 241 | -- | Truncate to an integer by removing the mantissa. 242 | foreign import truncated ∷ Decimal → Decimal 243 | 244 | -- | Euler's number. 245 | foreign import e ∷ Decimal 246 | 247 | -- | Pi, the ratio of a circle's circumference to its diameter. 248 | foreign import pi ∷ Decimal 249 | 250 | -- | The gamma function. 251 | foreign import gamma ∷ Decimal → Decimal 252 | 253 | -- | The factorial function. 254 | factorial ∷ Decimal → Decimal 255 | factorial n | n < zero = one / zero 256 | | otherwise = gamma $ ceil (n + one) 257 | 258 | -- | Inverse secant. 259 | asec ∷ Decimal → Decimal 260 | asec x = acos (one / x) 261 | 262 | -- | Inverse cosecant. 263 | acsc ∷ Decimal → Decimal 264 | acsc x = asin (one / x) 265 | 266 | -- | Inverse cotangent. 267 | -- | 268 | -- | Note that this is a multivalued function. The definition here 269 | -- | refers to the principal value of 'acot' without a discontinuity at x=0, and a domain 270 | -- | of (0, π). 271 | -- | See https://mathworld.wolfram.com/InverseCotangent.html for more information. 272 | acot ∷ Decimal → Decimal 273 | acot x = 274 | if x > zero 275 | then atan (one / x) 276 | else atan (one / x) + pi 277 | 278 | -- | Secant. 279 | sec ∷ Decimal → Decimal 280 | sec x = one / cos x 281 | 282 | -- | Cosecant. 283 | csc ∷ Decimal → Decimal 284 | csc x = one / sin x 285 | 286 | -- | Cotangent. 287 | cot ∷ Decimal → Decimal 288 | cot x = one / tan x 289 | 290 | -- | Hyperbolic cosecant. 291 | csch ∷ Decimal → Decimal 292 | csch x = one / sinh x 293 | 294 | -- | Hyperbolic secant. 295 | sech ∷ Decimal → Decimal 296 | sech x = one / cosh x 297 | 298 | -- | Hyperbolic cotangent. 299 | coth ∷ Decimal → Decimal 300 | coth x = one / tanh x 301 | 302 | -- | Inverse hyperbolic cosecant. 303 | acsch ∷ Decimal → Decimal 304 | acsch x = asinh (one / x) 305 | 306 | -- | Inverse hyperbolic secant. 307 | asech ∷ Decimal → Decimal 308 | asech x = acosh (one / x) 309 | 310 | -- | Inverse hyperbolic cotangent. 311 | acoth ∷ Decimal → Decimal 312 | acoth x = atanh (one / x) 313 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main (main) where 2 | 3 | import Prelude hiding (clamp, min, max) 4 | import Data.Decimal (Decimal, abs, clamp, fromInt, fromNumber, pow, fromString, 5 | toNumber, toString, toFixed, csc, sec, cot, acsc, asec, acot, csch, 6 | sech, coth, acsch, asech, acoth, acos, acosh, asin, asinh, atan, atanh, 7 | atan2, ceil, cos, cosh, exp, floor, ln, log10, max, 8 | min, modulo, round, truncated, sin, sinh, sqrt, tan, tanh, e, pi, gamma, 9 | toSignificantDigits, toDecimalPlaces, isInteger, isFinite, factorial) 10 | import Data.Int as Int 11 | import Data.Maybe (Maybe(..)) 12 | import Data.Number as N 13 | import Data.Number.Approximate ((≅)) 14 | import Effect (Effect) 15 | import Effect.Console (log) 16 | import Test.Assert (assert) 17 | import Test.QuickCheck (quickCheck) 18 | import Test.QuickCheck.Arbitrary (class Arbitrary, arbitrary) 19 | import Data.Foldable (for_) 20 | import Data.Tuple.Nested ((/\)) 21 | 22 | -- | Arbitrary instance for Decimal 23 | newtype TestDecimal = TestDecimal Decimal 24 | 25 | instance Arbitrary TestDecimal where 26 | arbitrary = (TestDecimal <<< fromNumber) <$> arbitrary 27 | 28 | -- | Test if a simple function holds before and after converting to Decimal. 29 | testFn ∷ (Decimal → Decimal) → (Number → Number) → Effect Unit 30 | testFn f g = quickCheck \(TestDecimal x) → toNumber (f x) ≅ g (toNumber x) 31 | 32 | -- | Test if a binary relation holds before and after converting to Decimal. 33 | testBinary ∷ (Decimal → Decimal → Decimal) 34 | → (Number → Number → Number) 35 | → Effect Unit 36 | testBinary f g = quickCheck \x y → toNumber ((fromNumber x) `f` (fromNumber y)) ≅ (x `g` y) 37 | 38 | assertAlmostEqual ∷ Decimal → Decimal → Effect Unit 39 | assertAlmostEqual x y = assert $ toNumber x ≅ toNumber y 40 | 41 | main ∷ Effect Unit 42 | main = do 43 | log "Simple arithmetic operations and conversions from Int" 44 | let two = one + one 45 | let three = two + one 46 | let four = three + one 47 | assert $ fromInt 3 == three 48 | assert $ two * two == four 49 | assert $ two * three * (three + four) == fromInt 42 50 | assert $ two - three == fromInt (-1) 51 | 52 | log "Parsing strings" 53 | assert $ fromString "2" == Just two 54 | assert $ fromString "a" == Nothing 55 | assert $ fromString "2.1" == Just (fromNumber 2.1) 56 | assert $ fromString "123456789" == Just (fromInt 123456789) 57 | assert $ fromString "1e7" == Just (fromInt 10000000) 58 | quickCheck \(TestDecimal a) → (fromString <<< toString) a == Just a 59 | 60 | log "toFixed" 61 | let a = fromNumber 123.456789 62 | assert $ toFixed 0 a == "123" 63 | assert $ toFixed 3 a == "123.457" 64 | assert $ toFixed 8 a == "123.45678900" 65 | 66 | log "Conversions between String, Int and Decimal should not lose precision" 67 | quickCheck \n → fromString (show n) == Just (fromInt n) 68 | quickCheck \n → Int.toNumber n == toNumber (fromInt n) 69 | 70 | log "Binary relations between integers should hold before and after converting to Decimal" 71 | testBinary (+) (+) 72 | testBinary (-) (-) 73 | testBinary (*) (*) 74 | testBinary (/) (/) 75 | testBinary mod mod 76 | testBinary div div 77 | 78 | log "It should perform multiplications which would lead to imprecise results using Int" 79 | assert $ Just (fromInt 333190782 * fromInt 1103515245) == fromString "367681107430471590" 80 | 81 | log "compare, (==)" 82 | assert $ one + one == two 83 | assert $ two /= one 84 | assert $ two > one 85 | assert $ not (two < one) 86 | assert $ not (two < two) 87 | assert $ two >= two 88 | assert $ two <= two 89 | 90 | log "toSignificantDigits" 91 | let a = fromNumber 123.456789 92 | assert $ toSignificantDigits 2 a == fromNumber 120.0 93 | assert $ toSignificantDigits 4 a == fromNumber 123.5 94 | assert $ toSignificantDigits 20 a == a 95 | 96 | log "toDecimalPlaces" 97 | let a = fromNumber 12.34567 98 | assert $ toDecimalPlaces 0 a == fromInt 12 99 | assert $ toDecimalPlaces 1 a == fromNumber 12.3 100 | assert $ toDecimalPlaces 2 a == fromNumber 12.35 101 | assert $ toDecimalPlaces 3 a == fromNumber 12.346 102 | assert $ toDecimalPlaces 4 a == fromNumber 12.3457 103 | assert $ toDecimalPlaces 5 a == a 104 | 105 | log "isInteger" 106 | assert $ isInteger (fromNumber 118327913791272.0) 107 | assert $ not $ isInteger (fromNumber 118327913791272.1) 108 | 109 | log "isFinite" 110 | assert $ isFinite (fromNumber 118327913791272.0) 111 | assert $ not $ isFinite (fromNumber 1.0 / fromNumber 0.0) 112 | assert $ not $ isFinite (sqrt $ fromNumber (-1.0)) 113 | 114 | log "pow should perform exponentiation" 115 | assert $ three `pow` four == fromInt 81 116 | assert $ four `pow` (fromNumber 0.5) == two 117 | assert $ four `pow` (fromNumber (-0.5)) == one / two 118 | assert $ three `pow` zero == one 119 | assert $ zero `pow` zero == one 120 | 121 | log "modulo" 122 | assert $ zero `modulo` three == zero 123 | assert $ one `modulo` three == one 124 | assert $ two `modulo` three == two 125 | assert $ three `modulo` three == zero 126 | assert $ four `modulo` three == one 127 | assert $ (-one) `modulo` three == two 128 | assert $ (-three) `modulo` three == zero 129 | assert $ one `modulo` (-three) == one 130 | 131 | log "Absolute value" 132 | quickCheck \(TestDecimal x) → abs x == if x > zero then x else -x 133 | 134 | log "clamp" 135 | assert $ clamp three four one == three 136 | assert $ clamp zero two zero == zero 137 | 138 | log "Other functions" 139 | testFn acos N.acos 140 | testFn asin N.asin 141 | testFn atan N.atan 142 | testFn cos N.cos 143 | testFn sin N.sin 144 | testFn tan N.tan 145 | testFn exp N.exp 146 | testFn ln N.log 147 | testFn ceil N.ceil 148 | testFn floor N.floor 149 | testFn round N.round 150 | testFn truncated \x → if x >= zero then N.floor x else N.ceil x 151 | testFn sqrt N.sqrt 152 | testFn cosh \x → 0.5 * (N.exp x + N.exp (-x)) 153 | testFn sinh \x → 0.5 * (N.exp x - N.exp (-x)) 154 | testFn tanh \x → (N.exp x - N.exp (-x)) / (N.exp x + N.exp (-x)) 155 | 156 | for_ [sin /\ csc, cos /\ sec, tan /\ cot, sinh /\ csch, cosh /\ sech, tanh /\ coth] \(f /\ f') → 157 | quickCheck \(TestDecimal x) → toNumber (f x * f' x) ≅ 1.0 158 | 159 | for_ [sin /\ asin, cos /\ acos, tan /\ atan, sinh /\ asinh, tanh /\ atanh, sech /\ asech, csch /\ acsch] \(f /\ f') → do 160 | testFn (f <<< f') identity 161 | testFn (f' <<< f) identity 162 | 163 | -- We need custom tests for these functions since the inverse function in 164 | -- each pair may fail the above test, because it may be given a value outside 165 | -- of its domain. 166 | assertAlmostEqual (fromNumber 2.823) (sec (asec (fromNumber 2.823))) 167 | assertAlmostEqual (fromNumber 3.1) (asec (sec (fromNumber 3.1))) 168 | 169 | assertAlmostEqual (fromNumber 1.5) (csc (acsc (fromNumber 1.5))) 170 | assertAlmostEqual (fromNumber 0.4) (acsc (csc (fromNumber 0.4))) 171 | 172 | assertAlmostEqual (fromNumber 2.8) (cot (acot (fromNumber 2.8))) 173 | assertAlmostEqual (fromNumber 1.33) (acot (cot (fromNumber 1.33))) 174 | 175 | assertAlmostEqual (fromNumber 5.0) (cosh (acosh (fromNumber 5.0))) 176 | assertAlmostEqual (fromNumber 3.4) (acosh (cosh (fromNumber 3.4))) 177 | 178 | assertAlmostEqual (fromNumber 2.0) (coth (acoth (fromNumber 2.0))) 179 | assertAlmostEqual (fromNumber 1.414) (acoth (coth (fromNumber 1.414))) 180 | 181 | testBinary atan2 N.atan2 182 | testBinary max N.max 183 | testBinary min N.min 184 | assert $ log10 (fromInt 1000) == fromInt 3 185 | 186 | log "e and pi" 187 | assert $ cos pi == -one 188 | assert $ ln (e `pow` two) == two 189 | 190 | log "gamma" 191 | let eps = fromNumber 1.0e-10 192 | approxEq yStr x = 193 | case fromString yStr of 194 | Just y → assert $ abs (x / y - one) < eps 195 | Nothing → do 196 | log "Could not parse string" 197 | assert false 198 | 199 | approxEq "1.24216934450430540491" (gamma (fromNumber 2.4)) 200 | approxEq "9.51350769866873183629" (gamma (fromNumber 0.1)) 201 | approxEq "2.65927187288003053999" (gamma (fromNumber (-1.4))) 202 | approxEq "9.33262154439441526817e+155" (gamma (fromNumber (100.0))) 203 | 204 | log "factorial" 205 | assert $ factorial zero == one 206 | assert $ factorial one == one 207 | assert $ factorial (fromInt 5) == fromInt (2 * 3 * 4 * 5) 208 | assert $ factorial (fromInt 10) == fromInt 3628800 209 | assert $ factorial (fromInt 10) == fromInt 3628800 210 | assert $ factorial (fromInt 15) == fromNumber 1307674368000.0 211 | approxEq "2432902008176640000" (factorial (fromInt 20)) 212 | approxEq "10888869450418352160768000000" (factorial (fromInt 27)) 213 | approxEq "263130836933693530167218012160000000" (factorial (fromInt 32)) 214 | approxEq "30414093201713378043612608166064768844377641568960512000000000000" (factorial (fromInt 50)) 215 | 216 | log "done" 217 | --------------------------------------------------------------------------------