├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmarks ├── AbsBenchmark.elm ├── AddBenchmark.elm ├── BenchmarkShared.elm ├── BigIntBenchmarks.elm ├── CompareBenchmark.elm ├── DivmodBenchmark.elm ├── FromIntBenchmark.elm ├── FromStringBenchmark.elm ├── MulBenchmark.elm ├── PowBenchmark.elm ├── ToStringBenchmark.elm └── elm.json ├── changelog ├── elm.json ├── src ├── BigInt.elm └── Constants.elm └── tests └── BigIntTests.elm /.gitignore: -------------------------------------------------------------------------------- 1 | # elm-package generated files 2 | elm-stuff/ 3 | # elm-repl generated files 4 | repl-temp-* 5 | # elm-make Test.elm generated files 6 | index.html 7 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 4 7 | - 6 8 | 9 | cache: 10 | directories: 11 | - tests/elm-stuff/build-artifacts 12 | - sysconfcpus 13 | 14 | env: 15 | - ELM_VERSION=0.18.0 16 | 17 | before_install: 18 | - | # epic build time improvement - see https://github.com/elm-lang/elm-compiler/issues/1473#issuecomment-245704142 19 | if [ ! -d sysconfcpus/bin ]; 20 | then 21 | git clone https://github.com/obmarg/libsysconfcpus.git; 22 | cd libsysconfcpus; 23 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; 24 | make && make install; 25 | cd ..; 26 | fi 27 | install: 28 | - npm install -g elm-use elm-test 29 | - elm-use $ELM_VERSION 30 | - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old 31 | - printf '%s\n\n' '#!/bin/bash' 'echo "Running elm-make with sysconfcpus -n 2"' '$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old "$@"' > $(npm config get prefix)/bin/elm-make 32 | - chmod +x $(npm config get prefix)/bin/elm-make 33 | - travis_retry elm-package install --yes 34 | - cd tests 35 | - travis_retry elm-package install --yes 36 | - cd .. 37 | 38 | script: 39 | #- elm-format --validate src tests examples readme-example 40 | - $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-test 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Javier Casas Velasco 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 | # bigint 2 | 3 | Elm's native integer type uses raw JavaScript integers which are limited in size ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)). Sometimes, we want more. 4 | 5 | This package provides a `BigInt` type and associated functions so that you can work with integers of unlimited size at the cost of some speed. Benchmarks included. 6 | 7 | ## Contributions 8 | are very welcome! 9 | 10 | ## Aknowledgements 11 | 12 | - Thank you [Javier Casas](https://github.com/javcasas) whose [elm-integer](https://github.com/javcasas/elm-integer) is the basis for this fork. 13 | - Thank you [gilbertkennen](https://github.com/gilbertkennen) whose [bigint](https://github.com/gilbertkennen/bigint) is basically the verbatim basis for this repository. 14 | - Thank you [hickscorp](https://github.com/hickscorp) whose [bigint](https://github.com/hickscorp/bigint) for the further work on top of the original BigInt library. 15 | -------------------------------------------------------------------------------- /benchmarks/AbsBenchmark.elm: -------------------------------------------------------------------------------- 1 | module AbsBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "abs" 17 | [ benchmark "positive 32-digit number" (\_ -> BI.abs bigInt) 18 | , benchmark "negative 32-digit number" (\_ -> BI.abs negativeBigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/AddBenchmark.elm: -------------------------------------------------------------------------------- 1 | module AddBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "add" 17 | [ benchmark "two positive 32-digit numbers" (\_ -> BI.add bigInt bigInt) 18 | , benchmark "two negative 32-digit numbers" (\_ -> BI.add negativeBigInt negativeBigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/BenchmarkShared.elm: -------------------------------------------------------------------------------- 1 | module BenchmarkShared exposing (bigInt, bigIntHexString, bigIntString, negativeBigInt) 2 | 3 | import BigInt as BI 4 | 5 | 6 | bigIntString : String 7 | bigIntString = 8 | "98765432100123456789987654321001234567899876543210012345678909" 9 | 10 | 11 | bigIntHexString : String 12 | bigIntHexString = 13 | "0xab5293028efd8c09b76aA37872902bbc1231def131" 14 | 15 | 16 | bigInt : BI.BigInt 17 | bigInt = 18 | BI.fromIntString bigIntString 19 | |> Maybe.withDefault (BI.fromInt 0) 20 | 21 | 22 | negativeBigInt : BI.BigInt 23 | negativeBigInt = 24 | BI.negate bigInt 25 | -------------------------------------------------------------------------------- /benchmarks/BigIntBenchmarks.elm: -------------------------------------------------------------------------------- 1 | module BigIntBenchmarks exposing (main) 2 | 3 | -- Individual Benchmarks 4 | 5 | import AbsBenchmark 6 | import AddBenchmark 7 | import Benchmark exposing (describe) 8 | import Benchmark.Runner exposing (BenchmarkProgram, program) 9 | import CompareBenchmark 10 | import DivmodBenchmark 11 | import FromIntBenchmark 12 | import FromStringBenchmark 13 | import MulBenchmark 14 | import ToStringBenchmark 15 | 16 | 17 | main : BenchmarkProgram 18 | main = 19 | program <| 20 | describe "BigInt" 21 | [ AbsBenchmark.suite 22 | , AddBenchmark.suite 23 | , CompareBenchmark.suite 24 | , DivmodBenchmark.suite 25 | , FromIntBenchmark.suite 26 | , FromStringBenchmark.suite 27 | , MulBenchmark.suite 28 | , ToStringBenchmark.suite 29 | ] 30 | -------------------------------------------------------------------------------- /benchmarks/CompareBenchmark.elm: -------------------------------------------------------------------------------- 1 | module CompareBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "compare" 17 | [ benchmark "one 32-digit number with itself" (\_ -> BI.compare bigInt bigInt) 18 | , benchmark "one positive and one negative 32-digit number" (\_ -> BI.compare bigInt negativeBigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/DivmodBenchmark.elm: -------------------------------------------------------------------------------- 1 | module DivmodBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "divmod" 17 | [ benchmark "one 32-digit number by 2" (\_ -> BI.divmod bigInt (BI.fromInt 2)) 18 | , benchmark "one 32-digit number by itself" (\_ -> BI.divmod bigInt bigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/FromIntBenchmark.elm: -------------------------------------------------------------------------------- 1 | module FromIntBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BigInt as BI 6 | 7 | 8 | main : BenchmarkProgram 9 | main = 10 | program suite 11 | 12 | 13 | suite : Benchmark 14 | suite = 15 | describe "fromInt" 16 | [ benchmark "MAX_SAFE_INTEGER" (\_ -> BI.fromInt 9007199254740991) 17 | ] 18 | -------------------------------------------------------------------------------- /benchmarks/FromStringBenchmark.elm: -------------------------------------------------------------------------------- 1 | module FromStringBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigIntHexString, bigIntString) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "fromString" 17 | [ benchmark "a 32-digit integer" (\_ -> BI.fromIntString bigIntString) 18 | , benchmark "a large hex string" (\_ -> BI.fromHexString bigIntHexString) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/MulBenchmark.elm: -------------------------------------------------------------------------------- 1 | module MulBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "mul" 17 | [ benchmark "positive 32-digit number by itself" (\_ -> BI.mul bigInt bigInt) 18 | , benchmark "negative 32-digit number by positive 32-digit number" (\_ -> BI.mul negativeBigInt bigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/PowBenchmark.elm: -------------------------------------------------------------------------------- 1 | module PowBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt, negativeBigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "pow" 17 | [ benchmark "positive 32-digit to the power 21" (\_ -> BI.pow bigInt (BI.fromInt 21)) 18 | , benchmark "negative 32-digit to the power 21" (\_ -> BI.pow negativeBigInt (BI.fromInt 21)) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/ToStringBenchmark.elm: -------------------------------------------------------------------------------- 1 | module ToStringBenchmark exposing (main, suite) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import BenchmarkShared exposing (bigInt) 6 | import BigInt as BI 7 | 8 | 9 | main : BenchmarkProgram 10 | main = 11 | program suite 12 | 13 | 14 | suite : Benchmark 15 | suite = 16 | describe "toString" 17 | [ benchmark "to int string" (\_ -> BI.toString bigInt) 18 | , benchmark "to hex string" (\_ -> BI.toHexString bigInt) 19 | ] 20 | -------------------------------------------------------------------------------- /benchmarks/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.2", 11 | "elm/core": "1.0.5", 12 | "elm/html": "1.0.0", 13 | "elm/regex": "1.0.0", 14 | "elm-community/list-extra": "8.3.0", 15 | "elm-community/maybe-extra": "5.2.0", 16 | "elm-community/result-extra": "2.4.0", 17 | "elm-explorations/benchmark": "1.0.2", 18 | "rtfeldman/elm-hex": "1.0.0" 19 | }, 20 | "indirect": { 21 | "BrianHicks/elm-trend": "2.1.3", 22 | "elm/json": "1.1.3", 23 | "elm/time": "1.0.0", 24 | "elm/url": "1.0.0", 25 | "elm/virtual-dom": "1.0.2", 26 | "mdgriffith/style-elements": "5.0.2", 27 | "robinheghan/murmur3": "1.0.0" 28 | } 29 | }, 30 | "test-dependencies": { 31 | "direct": {}, 32 | "indirect": {} 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /changelog: -------------------------------------------------------------------------------- 1 | 1.0.2 2 | + Updated to 0.19 3 | + Mixed case hex strings are now valid. 4 | + (BigInt.fromString "") no longer equals (BigInt.fromString "0") 5 | + mod renamed to modBy. Returns Maybe instead of crashing now. `modBy 0 n` == Nothing 6 | + Fixed bug where `BigInt.fromInt 1234567812345678 |> BigInt.toString` would cause a crash. 7 | + BigInt.toHexString now prepends "0x" or "-0x". Hex strings should be produced as they are consumed. -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "cmditch/elm-bigint", 4 | "summary": "Unlimited size integers", 5 | "license": "MIT", 6 | "version": "2.0.1", 7 | "exposed-modules": [ 8 | "BigInt" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0", 13 | "elm/regex": "1.0.0 <= v < 2.0.0", 14 | "elm-community/list-extra": "8.0.0 <= v < 9.0.0", 15 | "elm-community/maybe-extra": "5.0.0 <= v < 6.0.0", 16 | "rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0" 17 | }, 18 | "test-dependencies": { 19 | "elm/random": "1.0.0 <= v < 2.0.0", 20 | "elm-explorations/test": "1.1.0 <= v < 2.0.0" 21 | } 22 | } -------------------------------------------------------------------------------- /src/BigInt.elm: -------------------------------------------------------------------------------- 1 | module BigInt exposing 2 | ( BigInt 3 | , fromInt, fromIntString, fromHexString, toString, toHexString 4 | , add, sub, mul, div, modBy, divmod, pow 5 | , abs, negate 6 | , compare, gt, gte, lt, lte, max, min 7 | , isEven, isOdd 8 | ) 9 | 10 | {-| Infinite digits integers 11 | 12 | @docs BigInt 13 | 14 | 15 | # From/To 16 | 17 | @docs fromInt, fromIntString, fromHexString, toString, toHexString 18 | 19 | 20 | # Operations 21 | 22 | @docs add, sub, mul, div, modBy, divmod, pow 23 | 24 | 25 | # Sign 26 | 27 | @docs abs, negate 28 | 29 | 30 | # Comparison 31 | 32 | @docs compare, gt, gte, lt, lte, max, min 33 | 34 | 35 | # Misc 36 | 37 | @docs isEven, isOdd 38 | 39 | -} 40 | 41 | import Basics 42 | import Constants exposing (hexDigitMagnitude, maxDigitMagnitude, maxDigitValue) 43 | import Hex 44 | import List.Extra 45 | import Maybe exposing (Maybe) 46 | import Maybe.Extra 47 | import Regex 48 | 49 | 50 | {-| The sign of the bigInt 51 | -} 52 | type Sign 53 | = Positive 54 | | Negative 55 | | Zero 56 | 57 | 58 | eightHexDigits : BigInt 59 | eightHexDigits = 60 | mul (fromInt 2) (fromInt 0x80000000) 61 | 62 | 63 | signProduct : Sign -> Sign -> Sign 64 | signProduct x y = 65 | if x == Zero || y == Zero then 66 | Zero 67 | 68 | else if x == y then 69 | Positive 70 | 71 | else 72 | Negative 73 | 74 | 75 | signNegate : Sign -> Sign 76 | signNegate sign_ = 77 | case sign_ of 78 | Positive -> 79 | Negative 80 | 81 | Negative -> 82 | Positive 83 | 84 | Zero -> 85 | Zero 86 | 87 | 88 | signFromInt : Int -> Sign 89 | signFromInt x = 90 | case Basics.compare x 0 of 91 | LT -> 92 | Negative 93 | 94 | GT -> 95 | Positive 96 | 97 | EQ -> 98 | Zero 99 | 100 | 101 | 102 | {- From smallest to largest digit, all the digits are positive, no leading zeros -} 103 | 104 | 105 | {-| -} 106 | type BigInt 107 | = Pos Magnitude 108 | | Neg Magnitude 109 | | Zer 110 | 111 | 112 | {-| A list of safe-size `Int`s. with smaller magnitudes closer to the head. 113 | -} 114 | type Magnitude 115 | = Magnitude (List Int) 116 | 117 | 118 | mkBigInt : Sign -> Magnitude -> BigInt 119 | mkBigInt s ((Magnitude digits) as mag) = 120 | if List.isEmpty digits then 121 | Zer 122 | 123 | else 124 | case s of 125 | Zero -> 126 | Zer 127 | 128 | Positive -> 129 | Pos mag 130 | 131 | Negative -> 132 | Neg mag 133 | 134 | 135 | type BigIntNotNormalised 136 | = BigIntNotNormalised Sign MagnitudeNotNormalised 137 | 138 | 139 | type MagnitudeNotNormalised 140 | = MagnitudeNotNormalised (List Int) 141 | 142 | 143 | mkBigIntNotNormalised : Sign -> List Int -> BigIntNotNormalised 144 | mkBigIntNotNormalised s digits = 145 | BigIntNotNormalised s (MagnitudeNotNormalised digits) 146 | 147 | 148 | toDigits : BigInt -> List Int 149 | toDigits bigInt = 150 | case bigInt of 151 | Zer -> 152 | [] 153 | 154 | Pos (Magnitude ds) -> 155 | ds 156 | 157 | Neg (Magnitude ds) -> 158 | ds 159 | 160 | 161 | baseDigit : Int 162 | baseDigit = 163 | maxDigitValue + 1 164 | 165 | 166 | {-| Makes a BigInt from an Int 167 | -} 168 | fromInt : Int -> BigInt 169 | fromInt x = 170 | BigIntNotNormalised (signFromInt x) (MagnitudeNotNormalised [ Basics.abs x ]) 171 | |> normalise 172 | 173 | 174 | {-| Makes a BigInt from an integer string, positive or negative 175 | 176 | fromIntString "123" == Just (BigInt.Pos ...) 177 | fromIntString "-123" == Just (BigInt.Neg ...) 178 | fromIntString "" == Nothing 179 | fromIntString "this is not a number :P" == Nothing 180 | 181 | -} 182 | fromIntString : String -> Maybe BigInt 183 | fromIntString x = 184 | case String.toList (String.toLower x) of 185 | [] -> 186 | Nothing 187 | 188 | '-' :: [] -> 189 | Nothing 190 | 191 | '-' :: xs -> 192 | fromString_ xs 193 | |> Maybe.map (mkBigInt Negative) 194 | 195 | '+' :: [] -> 196 | Nothing 197 | 198 | '+' :: xs -> 199 | fromString_ xs 200 | |> Maybe.map (mkBigInt Positive) 201 | 202 | xs -> 203 | fromString_ xs 204 | |> Maybe.map (mkBigInt Positive) 205 | 206 | 207 | {-| Makes a BigInt from a base16 hex string, positive or negative. 208 | 209 | fromHexString "4b6" == Just (BigInt.Pos ...) 210 | fromHexString "-13d" == Just (BigInt.Neg ...) 211 | 212 | fromHexString "0x456" == Just (BigInt.Pos ...) 213 | fromHexString "-0x123" == Just (BigInt.Neg ...) 214 | 215 | fromHexString "R2D2" == Nothing 216 | fromHexString "0xC3P0" == Nothing 217 | fromHexString "0x" == Nothing 218 | fromHexString "" == Nothing 219 | 220 | **Note:** String can be prepended with or without any combination of "0x", and "+" or "-". 221 | 222 | -} 223 | fromHexString : String -> Maybe BigInt 224 | fromHexString x = 225 | case String.toList (String.toLower x) of 226 | [] -> 227 | Nothing 228 | 229 | '-' :: '0' :: 'x' :: [] -> 230 | Nothing 231 | 232 | '-' :: '0' :: 'x' :: xs -> 233 | fromHexString_ xs 234 | |> Maybe.map (mul (fromInt -1)) 235 | 236 | '-' :: [] -> 237 | Nothing 238 | 239 | '-' :: xs -> 240 | fromHexString_ xs 241 | |> Maybe.map (mul (fromInt -1)) 242 | 243 | '+' :: [] -> 244 | Nothing 245 | 246 | '+' :: xs -> 247 | fromHexString_ xs 248 | 249 | '0' :: 'x' :: [] -> 250 | Nothing 251 | 252 | '0' :: 'x' :: xs -> 253 | fromHexString_ xs 254 | 255 | xs -> 256 | fromHexString_ xs 257 | 258 | 259 | {-| Split a number string into chunks of `maxDigitMagnitude` from smallest digits. 260 | Turn those into integers and store as a Magnitude. 261 | -} 262 | fromString_ : List Char -> Maybe Magnitude 263 | fromString_ x = 264 | case Regex.contains (Maybe.withDefault Regex.never (Regex.fromString "^[0-9]")) <| String.fromList x of 265 | True -> 266 | List.reverse x 267 | |> List.Extra.greedyGroupsOf maxDigitMagnitude 268 | |> List.map (List.reverse >> String.fromList >> String.toInt) 269 | |> Maybe.Extra.combine 270 | |> Maybe.map (emptyZero << Magnitude) 271 | 272 | False -> 273 | Nothing 274 | 275 | 276 | fromHexString_ : List Char -> Maybe BigInt 277 | fromHexString_ x = 278 | case Regex.contains (Maybe.withDefault Regex.never (Regex.fromString "^[0-9A-Fa-f]")) <| String.fromList x of 279 | True -> 280 | List.reverse x 281 | |> List.Extra.greedyGroupsOf hexDigitMagnitude 282 | |> List.map (List.reverse >> String.fromList >> Hex.fromString >> Result.toMaybe) 283 | |> Maybe.Extra.combine 284 | |> Maybe.map 285 | (List.reverse 286 | >> List.foldl (\e s -> mul s eightHexDigits |> add (fromInt e)) zero 287 | ) 288 | 289 | False -> 290 | Nothing 291 | 292 | 293 | emptyZero : Magnitude -> Magnitude 294 | emptyZero (Magnitude xs) = 295 | case List.Extra.dropWhile ((==) 0) xs of 296 | [] -> 297 | Magnitude [] 298 | 299 | _ -> 300 | Magnitude xs 301 | 302 | 303 | {-| Adds two BigInts 304 | -} 305 | add : BigInt -> BigInt -> BigInt 306 | add a b = 307 | let 308 | (BigIntNotNormalised _ ma) = 309 | toPositiveSign a 310 | 311 | (BigIntNotNormalised _ mb) = 312 | toPositiveSign b 313 | 314 | (MagnitudePair pairs) = 315 | sameSizeNotNormalized ma mb 316 | 317 | added = 318 | List.map (\( a_, b_ ) -> a_ + b_) pairs 319 | in 320 | normalise <| BigIntNotNormalised Positive (MagnitudeNotNormalised added) 321 | 322 | 323 | {-| Changes the sign of an BigInt 324 | -} 325 | negate : BigInt -> BigInt 326 | negate bigInt = 327 | case bigInt of 328 | Zer -> 329 | Zer 330 | 331 | Pos mag -> 332 | Neg mag 333 | 334 | Neg mag -> 335 | Pos mag 336 | 337 | 338 | {-| Absolute value 339 | -} 340 | abs : BigInt -> BigInt 341 | abs bigInt = 342 | case bigInt of 343 | Zer -> 344 | Zer 345 | 346 | Neg mag -> 347 | Pos mag 348 | 349 | i -> 350 | i 351 | 352 | 353 | {-| Substracts the second BigInt from the first 354 | -} 355 | sub : BigInt -> BigInt -> BigInt 356 | sub a b = 357 | add a (negate b) 358 | 359 | 360 | {-| Multiplies two BigInts 361 | -} 362 | mul : BigInt -> BigInt -> BigInt 363 | mul int1 int2 = 364 | mkBigInt 365 | (signProduct (sign int1) (sign int2)) 366 | (mulMagnitudes (magnitude int1) (magnitude int2)) 367 | 368 | 369 | magnitude : BigInt -> Magnitude 370 | magnitude bigInt = 371 | case bigInt of 372 | Zer -> 373 | Magnitude [] 374 | 375 | Pos mag -> 376 | mag 377 | 378 | Neg mag -> 379 | mag 380 | 381 | 382 | mulMagnitudes : Magnitude -> Magnitude -> Magnitude 383 | mulMagnitudes (Magnitude mag1) (Magnitude mag2) = 384 | case mag1 of 385 | [] -> 386 | Magnitude [] 387 | 388 | m :: [] -> 389 | mulSingleDigit (Magnitude mag2) m 390 | 391 | m :: mx -> 392 | let 393 | accum = 394 | mulSingleDigit (Magnitude mag2) m 395 | 396 | (Magnitude rest) = 397 | mulMagnitudes (Magnitude mx) (Magnitude mag2) 398 | 399 | bigInt = 400 | add 401 | (mkBigInt Positive accum) 402 | (mkBigInt Positive (Magnitude (0 :: rest))) 403 | in 404 | magnitude bigInt 405 | 406 | 407 | mulSingleDigit : Magnitude -> Int -> Magnitude 408 | mulSingleDigit (Magnitude xs) d = 409 | xs 410 | |> List.map ((*) d) 411 | |> MagnitudeNotNormalised 412 | |> normaliseMagnitude 413 | 414 | 415 | {-| Compares two BigInts 416 | -} 417 | compare : BigInt -> BigInt -> Order 418 | compare int1 int2 = 419 | case ( int1, int2 ) of 420 | ( Pos (Magnitude mag1), Pos (Magnitude mag2) ) -> 421 | compareMagnitude 0 0 mag1 mag2 422 | 423 | ( Pos _, _ ) -> 424 | GT 425 | 426 | ( Neg (Magnitude mag1), Neg (Magnitude mag2) ) -> 427 | compareMagnitude 0 0 mag1 mag2 428 | |> orderNegate 429 | 430 | ( Neg _, _ ) -> 431 | LT 432 | 433 | ( Zer, Pos _ ) -> 434 | LT 435 | 436 | ( Zer, Zer ) -> 437 | EQ 438 | 439 | ( Zer, Neg _ ) -> 440 | GT 441 | 442 | 443 | compareMagnitude : Int -> Int -> List Int -> List Int -> Order 444 | compareMagnitude x y xs ys = 445 | case ( xs, ys ) of 446 | ( [], [] ) -> 447 | Basics.compare x y 448 | 449 | ( [], _ ) -> 450 | LT 451 | 452 | ( _, [] ) -> 453 | GT 454 | 455 | ( x_ :: xss, y_ :: yss ) -> 456 | if x_ == y_ then 457 | compareMagnitude x y xss yss 458 | 459 | else 460 | compareMagnitude x_ y_ xss yss 461 | 462 | 463 | orderNegate : Order -> Order 464 | orderNegate x = 465 | case x of 466 | LT -> 467 | GT 468 | 469 | EQ -> 470 | EQ 471 | 472 | GT -> 473 | LT 474 | 475 | 476 | {-| Less than 477 | -} 478 | lt : BigInt -> BigInt -> Bool 479 | lt x y = 480 | compare x y == LT 481 | 482 | 483 | {-| Greater than 484 | -} 485 | gt : BigInt -> BigInt -> Bool 486 | gt x y = 487 | compare x y == GT 488 | 489 | 490 | {-| Greater than or equals 491 | -} 492 | gte : BigInt -> BigInt -> Bool 493 | gte x y = 494 | not (lt x y) 495 | 496 | 497 | {-| Less than or equals 498 | -} 499 | lte : BigInt -> BigInt -> Bool 500 | lte x y = 501 | not (gt x y) 502 | 503 | 504 | {-| Returns the largest of two BigInts 505 | -} 506 | max : BigInt -> BigInt -> BigInt 507 | max x y = 508 | if lt x y then 509 | y 510 | 511 | else 512 | x 513 | 514 | 515 | {-| Returns the smallest of two BigInts 516 | -} 517 | min : BigInt -> BigInt -> BigInt 518 | min x y = 519 | if gt x y then 520 | y 521 | 522 | else 523 | x 524 | 525 | 526 | {-| Convert the BigInt to an integer string 527 | -} 528 | toString : BigInt -> String 529 | toString bigInt = 530 | case bigInt of 531 | Zer -> 532 | "0" 533 | 534 | Pos mag -> 535 | revMagnitudeToString mag 536 | 537 | Neg mag -> 538 | "-" ++ revMagnitudeToString mag 539 | 540 | 541 | fillZeroes : Int -> String 542 | fillZeroes = 543 | String.padLeft maxDigitMagnitude '0' << String.fromInt 544 | 545 | 546 | revMagnitudeToString : Magnitude -> String 547 | revMagnitudeToString (Magnitude digits) = 548 | case List.reverse digits of 549 | [] -> 550 | "0" 551 | 552 | x :: xs -> 553 | String.concat <| String.fromInt x :: List.map fillZeroes xs 554 | 555 | 556 | {-| Convert the BigInt to a hex string. 557 | 558 | toHexString (BigInt.fromInt 255) == "ff" 559 | 560 | **Note:** "0x" will NOT be prepended to the output. 561 | 562 | -} 563 | toHexString : BigInt -> String 564 | toHexString bigInt = 565 | case bigInt of 566 | Zer -> 567 | "0" 568 | 569 | Pos mag -> 570 | if mag == Magnitude [] then 571 | "0" 572 | 573 | else 574 | hexMagnitudeToString (Pos mag) 575 | 576 | Neg mag -> 577 | "-" ++ toHexString (mul (fromInt -1) bigInt) 578 | 579 | 580 | 581 | -- Shortcut conversion to int for hex handling 582 | 583 | 584 | bigIntToInt_ : BigInt -> Int 585 | bigIntToInt_ bigInt = 586 | case bigInt of 587 | Zer -> 588 | 0 589 | 590 | Pos (Magnitude [ a ]) -> 591 | a 592 | 593 | Pos (Magnitude [ a, b ]) -> 594 | b * (10 ^ maxDigitMagnitude) + a 595 | 596 | _ -> 597 | -- Note: In Elm 0.18, this last case was `Debug.crash "No suitable shortcut conversion in hexMagnitudeToString"` 598 | -- Using "impossible default value" instead. Should be impossible if this internal function is used correctly. 599 | -- Fuzz testing is very helpful. 600 | 42 601 | 602 | 603 | hexMagnitudeToString : BigInt -> String 604 | hexMagnitudeToString bigInt = 605 | case divmod bigInt eightHexDigits of 606 | Nothing -> 607 | -- Another "impossible default value" instead of Debug.crash 608 | "Failure converting BigInt to hex string. Should be impossible. Open up issue on the elm-bigint repo." 609 | 610 | Just ( d, r ) -> 611 | let 612 | rString = 613 | Hex.toString (bigIntToInt_ r) 614 | in 615 | if d == fromInt 0 then 616 | rString 617 | 618 | else 619 | hexMagnitudeToString d ++ String.padLeft 8 '0' rString 620 | 621 | 622 | {-| BigInt division. Produces 0 when dividing by 0 (like (//)). 623 | -} 624 | div : BigInt -> BigInt -> BigInt 625 | div num den = 626 | divmod num den 627 | |> Maybe.map Tuple.first 628 | |> Maybe.withDefault zero 629 | 630 | 631 | {-| Modulus. 632 | 633 | modBy (BigInt.fromInt 3) (BigInt.fromInt 3) 634 | 635 | **Note:** This function returns negative values when 636 | the second argument is negative, unlike Basics.modBy. 637 | 638 | -} 639 | modBy : BigInt -> BigInt -> Maybe BigInt 640 | modBy modulus x = 641 | divmod x modulus |> Maybe.map Tuple.second 642 | 643 | 644 | {-| Square. 645 | -} 646 | square : BigInt -> BigInt 647 | square num = 648 | mul num num 649 | 650 | 651 | {-| Parity Check - Even. 652 | -} 653 | isEven : BigInt -> Bool 654 | isEven num = 655 | let 656 | even i = 657 | Basics.modBy 2 i == 0 658 | in 659 | case num of 660 | Zer -> 661 | True 662 | 663 | Pos (Magnitude mag) -> 664 | even (List.head mag |> Maybe.withDefault 0) 665 | 666 | Neg (Magnitude mag) -> 667 | even (List.head mag |> Maybe.withDefault 0) 668 | 669 | 670 | {-| Parity Check - Odd. 671 | -} 672 | isOdd : BigInt -> Bool 673 | isOdd num = 674 | not (isEven num) 675 | 676 | 677 | {-| Power/Exponentiation. 678 | -} 679 | pow : BigInt -> BigInt -> BigInt 680 | pow base exp = 681 | powHelp one base exp 682 | 683 | 684 | {-| Power helper, for sake of tail-recursion. 685 | -} 686 | powHelp : BigInt -> BigInt -> BigInt -> BigInt 687 | powHelp work num exp = 688 | case exp of 689 | Zer -> 690 | one 691 | 692 | Neg _ -> 693 | Zer 694 | 695 | Pos _ -> 696 | if exp == one then 697 | mul work num 698 | 699 | else if isEven exp then 700 | powHelp work (square num) (div exp two) 701 | 702 | else 703 | powHelp (mul num work) (square num) (div (sub exp one) two) 704 | 705 | 706 | {-| Division and modulus 707 | -} 708 | divmod : BigInt -> BigInt -> Maybe ( BigInt, BigInt ) 709 | divmod num den = 710 | if den == zero then 711 | Nothing 712 | 713 | else 714 | let 715 | cand_l = 716 | List.length (toDigits num) - List.length (toDigits den) + 1 717 | 718 | ( d, m ) = 719 | divMod_ 720 | (Basics.max 0 cand_l) 721 | (abs num) 722 | (abs den) 723 | in 724 | Just 725 | ( mkBigInt (signProduct (sign num) (sign den)) (magnitude d) 726 | , mkBigInt (sign num) (magnitude m) 727 | ) 728 | 729 | 730 | divmodDigit : BigInt -> BigInt -> BigInt -> ( BigInt, BigInt ) 731 | divmodDigit padding x y = 732 | divmodDigit_ (2 ^ maxDigitBits) padding x y 733 | 734 | 735 | divmodDigit_ : Int -> BigInt -> BigInt -> BigInt -> ( BigInt, BigInt ) 736 | divmodDigit_ to_test padding num den = 737 | if to_test == 0 then 738 | ( zero, num ) 739 | 740 | else 741 | let 742 | x = 743 | fromInt to_test 744 | 745 | candidate = 746 | mul (mul x den) padding 747 | 748 | ( newdiv, newmod ) = 749 | if lte candidate num then 750 | ( mul x padding, sub num candidate ) 751 | 752 | else 753 | ( zero, num ) 754 | 755 | ( restdiv, restmod ) = 756 | divmodDigit_ (to_test // 2) padding newmod den 757 | in 758 | ( add newdiv restdiv, restmod ) 759 | 760 | 761 | divMod_ : Int -> BigInt -> BigInt -> ( BigInt, BigInt ) 762 | divMod_ n num den = 763 | if n == 0 then 764 | divmodDigit (padDigits n) num den 765 | 766 | else 767 | let 768 | ( cdiv, cmod ) = 769 | divmodDigit (padDigits n) num den 770 | 771 | ( rdiv, rmod ) = 772 | divMod_ (n - 1) cmod den 773 | in 774 | ( add cdiv rdiv, rmod ) 775 | 776 | 777 | maxDigitBits : Int 778 | maxDigitBits = 779 | maxDigitValue 780 | |> toFloat 781 | |> logBase 2 782 | |> ceiling 783 | 784 | 785 | padDigits : Int -> BigInt 786 | padDigits n = 787 | repeatedly (mul (fromInt baseDigit)) one n 788 | 789 | 790 | repeatedly : (a -> a) -> a -> Int -> a 791 | repeatedly f x n = 792 | List.foldl (always f) x (List.range 1 n) 793 | 794 | 795 | sign : BigInt -> Sign 796 | sign bigInt = 797 | case bigInt of 798 | Zer -> 799 | Zero 800 | 801 | Pos _ -> 802 | Positive 803 | 804 | Neg _ -> 805 | Negative 806 | 807 | 808 | zero : BigInt 809 | zero = 810 | fromInt 0 811 | 812 | 813 | one : BigInt 814 | one = 815 | fromInt 1 816 | 817 | 818 | two : BigInt 819 | two = 820 | fromInt 2 821 | 822 | 823 | {-| We can perform operations more quickly if we don't worry about keeping things in final compressed form. 824 | This takes a messed up number and cleans it up. 825 | -} 826 | normalise : BigIntNotNormalised -> BigInt 827 | normalise (BigIntNotNormalised s digits) = 828 | let 829 | (Magnitude normalisedMag) = 830 | normaliseMagnitude digits 831 | in 832 | if isNegativeMagnitude normalisedMag then 833 | normalise (mkBigIntNotNormalised (signNegate s) (reverseMagnitude normalisedMag)) 834 | 835 | else 836 | mkBigInt s (Magnitude normalisedMag) 837 | 838 | 839 | normaliseMagnitude : MagnitudeNotNormalised -> Magnitude 840 | normaliseMagnitude (MagnitudeNotNormalised xs) = 841 | Magnitude (xs |> normaliseDigitList 0 |> dropZeroes) 842 | 843 | 844 | normaliseDigitList : Int -> List Int -> List Int 845 | normaliseDigitList carry xs = 846 | case xs of 847 | [] -> 848 | if carry > baseDigit then 849 | normaliseDigitList 0 [ carry ] 850 | 851 | else 852 | [ carry ] 853 | 854 | x :: xs_ -> 855 | let 856 | ( newCarry, x_ ) = 857 | normaliseDigit (x + carry) 858 | in 859 | x_ :: normaliseDigitList newCarry xs_ 860 | 861 | 862 | normaliseDigit : Int -> ( Int, Int ) 863 | normaliseDigit x = 864 | if x < 0 then 865 | normaliseDigit (x + baseDigit) 866 | |> Tuple.mapFirst ((+) -1) 867 | 868 | else 869 | ( x // baseDigit, remainderBy baseDigit x ) 870 | 871 | 872 | dropZeroes : List Int -> List Int 873 | dropZeroes = 874 | List.Extra.dropWhileRight ((==) 0) 875 | 876 | 877 | toPositiveSign : BigInt -> BigIntNotNormalised 878 | toPositiveSign bigInt = 879 | case bigInt of 880 | Zer -> 881 | mkBigIntNotNormalised Zero [] 882 | 883 | Neg (Magnitude digits) -> 884 | mkBigIntNotNormalised Positive (reverseMagnitude digits) 885 | 886 | Pos (Magnitude digits) -> 887 | mkBigIntNotNormalised Positive digits 888 | 889 | 890 | isNegativeMagnitude : List Int -> Bool 891 | isNegativeMagnitude digits = 892 | case List.Extra.last digits of 893 | Nothing -> 894 | False 895 | 896 | Just x -> 897 | x < 0 898 | 899 | 900 | reverseMagnitude : List Int -> List Int 901 | reverseMagnitude = 902 | List.map Basics.negate 903 | 904 | 905 | {-| Magnitudes can be different sizes. This represents a pair of magnitudes with everything aligned. 906 | -} 907 | type MagnitudePair 908 | = MagnitudePair (List ( Int, Int )) 909 | 910 | 911 | sameSizeNotNormalized : MagnitudeNotNormalised -> MagnitudeNotNormalised -> MagnitudePair 912 | sameSizeNotNormalized (MagnitudeNotNormalised xs) (MagnitudeNotNormalised ys) = 913 | MagnitudePair <| sameSizeRaw xs ys 914 | 915 | 916 | sameSizeRaw : List Int -> List Int -> List ( Int, Int ) 917 | sameSizeRaw xs ys = 918 | case ( xs, ys ) of 919 | ( [], [] ) -> 920 | [] 921 | 922 | ( x :: xs_, [] ) -> 923 | ( x, 0 ) :: sameSizeRaw xs_ [] 924 | 925 | ( [], y :: ys_ ) -> 926 | ( 0, y ) :: sameSizeRaw [] ys_ 927 | 928 | ( x :: xs_, y :: ys_ ) -> 929 | ( x, y ) :: sameSizeRaw xs_ ys_ 930 | -------------------------------------------------------------------------------- /src/Constants.elm: -------------------------------------------------------------------------------- 1 | module Constants exposing 2 | ( hexDigitMagnitude 3 | , maxDigitMagnitude 4 | , maxDigitValue 5 | ) 6 | 7 | {-| Seven base-10 digits is the most we can have where x \* x < the JS bigInt limit. 8 | 99999999 > sqrt(MAX\_SAFE\_INTEGER) > 9999999 9 | A slightly higher number is possible, but would require a major reworking of the string functions. 10 | -} 11 | 12 | 13 | maxDigitValue : Int 14 | maxDigitValue = 15 | -1 + 10 ^ maxDigitMagnitude 16 | 17 | 18 | maxDigitMagnitude : Int 19 | maxDigitMagnitude = 20 | 7 21 | 22 | 23 | hexDigitMagnitude : Int 24 | hexDigitMagnitude = 25 | 8 26 | -------------------------------------------------------------------------------- /tests/BigIntTests.elm: -------------------------------------------------------------------------------- 1 | module BigIntTests exposing (absTests, addTests, compareTests, divmodTests, fromTests, integer, isEvenTests, isOddTests, maxTests, minTests, minusOne, mulTests, negateTests, nonZeroInteger, one, powTests, roundRobinTests, singleNonZeroInteger, smallInt, smallPositiveIntegers, stringTests, subTests, tinyInt, tinyPositiveInt, zero) 2 | 3 | import BigInt exposing (..) 4 | import Constants exposing (maxDigitValue) 5 | import Expect 6 | import Fuzz exposing (Fuzzer, int, intRange, tuple) 7 | import Hex 8 | import Maybe exposing (Maybe) 9 | import Random 10 | import String 11 | import Test exposing (..) 12 | 13 | 14 | integer : Fuzzer BigInt 15 | integer = 16 | Fuzz.map fromInt int 17 | 18 | 19 | maxIntRange : Fuzzer Int 20 | maxIntRange = 21 | intRange Random.minInt Random.maxInt 22 | 23 | 24 | trickyIntRange : Fuzzer Int 25 | trickyIntRange = 26 | intRange 1234567811345670 1234567812345678 27 | 28 | 29 | smallPositiveIntegers : Fuzzer Int 30 | smallPositiveIntegers = 31 | intRange 0 Random.maxInt 32 | 33 | 34 | singleNonZeroInteger : Fuzzer BigInt 35 | singleNonZeroInteger = 36 | integer 37 | |> Fuzz.map 38 | (\i -> 39 | if i == zero then 40 | one 41 | 42 | else 43 | i 44 | ) 45 | 46 | 47 | nonZeroInteger : Fuzzer BigInt 48 | nonZeroInteger = 49 | Fuzz.map2 mul singleNonZeroInteger singleNonZeroInteger 50 | 51 | 52 | zero : BigInt 53 | zero = 54 | fromInt 0 55 | 56 | 57 | one : BigInt 58 | one = 59 | fromInt 1 60 | 61 | 62 | minusOne : BigInt 63 | minusOne = 64 | fromInt -1 65 | 66 | 67 | smallInt : Fuzzer Int 68 | smallInt = 69 | intRange (Basics.negate maxDigitValue) maxDigitValue 70 | 71 | 72 | tinyInt : Fuzzer Int 73 | tinyInt = 74 | intRange -5 5 75 | 76 | 77 | tinyPositiveInt : Fuzzer Int 78 | tinyPositiveInt = 79 | intRange 0 11 80 | 81 | 82 | fromTests : Test 83 | fromTests = 84 | describe "from" 85 | [ test "fromString 9999999 = fromInt 9999999" <| 86 | \_ -> 87 | let 88 | fromString = 89 | BigInt.fromIntString "9999999" 90 | 91 | fromInt = 92 | BigInt.fromInt 9999999 93 | in 94 | Expect.equal fromString (Just fromInt) 95 | , test "fromString 10000000 = fromInt 10000000" <| 96 | \_ -> 97 | let 98 | fromString = 99 | BigInt.fromIntString "10000000" 100 | 101 | fromInt = 102 | BigInt.fromInt 10000000 103 | in 104 | Expect.equal fromString (Just fromInt) 105 | , test "fromString 10000001 = fromInt 10000001" <| 106 | \_ -> 107 | let 108 | fromString = 109 | BigInt.fromIntString "10000001" 110 | 111 | fromInt = 112 | BigInt.fromInt 10000001 113 | in 114 | Expect.equal fromString (Just fromInt) 115 | , test "fromHexString 0x2386f26fc10000 = mul (fromInt 100000000) (fromInt 100000000)" <| 116 | \_ -> 117 | let 118 | fromString = 119 | BigInt.fromHexString "0x2386f26fc10000" 120 | 121 | midLargeInt = 122 | BigInt.fromInt 100000000 123 | 124 | fromInt = 125 | BigInt.mul midLargeInt midLargeInt 126 | in 127 | Expect.equal fromString (Just fromInt) 128 | , test "fromHexString 2386f26fc10000 = mul (fromInt 100000000) (fromInt 100000000)" <| 129 | \_ -> 130 | let 131 | fromString = 132 | BigInt.fromHexString "2386f26fc10000" 133 | 134 | midLargeInt = 135 | BigInt.fromInt 100000000 136 | 137 | fromInt = 138 | BigInt.mul midLargeInt midLargeInt 139 | in 140 | Expect.equal fromString (Just fromInt) 141 | , test "fromString 0 = fromInt 0" <| 142 | \_ -> Expect.equal (BigInt.fromIntString "0") (Just <| BigInt.fromInt 0) 143 | , test "fromString \"\" = Nothing" <| 144 | \_ -> Expect.equal (BigInt.fromIntString "") Nothing 145 | , test "fromString + = Nothing" <| 146 | \_ -> Expect.equal (BigInt.fromIntString "+") Nothing 147 | , test "fromString - = Nothing" <| 148 | \_ -> Expect.equal (BigInt.fromIntString "-") Nothing 149 | , test "fromHexString 0x0 = fromInt 0" <| 150 | \_ -> Expect.equal (BigInt.fromHexString "0x0") (Just <| BigInt.fromInt 0) 151 | , test "fromHexString -0x0 = fromInt 0" <| 152 | \_ -> Expect.equal (BigInt.fromHexString "-0x0") (Just <| BigInt.fromInt 0) 153 | , test "fromHexString \"\" = Nothing" <| 154 | \_ -> Expect.equal (BigInt.fromHexString "") Nothing 155 | , test "fromHexString + = Nothing" <| 156 | \_ -> Expect.equal (BigInt.fromHexString "+") Nothing 157 | , test "fromHexString - = Nothing" <| 158 | \_ -> Expect.equal (BigInt.fromHexString "-") Nothing 159 | , test "fromHexString 0x = Nothing" <| 160 | \_ -> Expect.equal (BigInt.fromHexString "0x") Nothing 161 | , test "fromHexString --0x0 = Nothing" <| 162 | \_ -> Expect.equal (BigInt.fromHexString "--0x0") Nothing 163 | , test "fromIntString --23 = Nothing" <| 164 | \_ -> Expect.equal (BigInt.fromIntString "--23") Nothing 165 | ] 166 | 167 | 168 | roundRobinTests : Test 169 | roundRobinTests = 170 | let 171 | complexRoundRobin int_ = 172 | fromInt int_ 173 | |> toHexString 174 | |> fromHexString 175 | |> Maybe.andThen (toString >> fromIntString) 176 | in 177 | describe "complex round robin: fromInt -> toHexString -> fromHexString -> toString -> fromString" 178 | [ fuzz maxIntRange "large int range" <| 179 | \x -> 180 | complexRoundRobin x 181 | |> Expect.equal (Just <| fromInt x) 182 | , fuzz trickyIntRange "tricky int range" <| 183 | \x -> 184 | complexRoundRobin x 185 | |> Expect.equal (Just <| fromInt <| x) 186 | ] 187 | 188 | 189 | addTests : Test 190 | addTests = 191 | describe "addition" 192 | [ fuzz (tuple ( smallInt, smallInt )) "add x y = x + y for small numbers" <| 193 | \( x, y ) -> 194 | add (fromInt x) (fromInt y) 195 | |> Expect.equal (fromInt (x + y)) 196 | , fuzz (tuple ( integer, integer )) "x + y + (-y) = x" <| 197 | \( x, y ) -> 198 | add x y 199 | |> add (BigInt.negate y) 200 | |> Expect.equal x 201 | , fuzz (tuple ( integer, integer )) "a + b = b + a" <| 202 | \( a, b ) -> Expect.equal (add a b) (add b a) 203 | ] 204 | 205 | 206 | negateTests : Test 207 | negateTests = 208 | describe "negate" 209 | [ fuzz int "negate x = -x; x >= 0" <| 210 | \x -> 211 | let 212 | y = 213 | Basics.abs x 214 | in 215 | fromInt y 216 | |> BigInt.negate 217 | |> Expect.equal (fromInt (-1 * y)) 218 | , fuzz int "negate (-x) = x; x >= 0" <| 219 | \x -> 220 | let 221 | y = 222 | Basics.abs x * -1 223 | in 224 | fromInt y 225 | |> BigInt.negate 226 | |> Expect.equal (fromInt (-1 * y)) 227 | , fuzz integer "negate (negate x) = x" <| 228 | \a -> 229 | a 230 | |> BigInt.negate 231 | |> BigInt.negate 232 | |> Expect.equal a 233 | ] 234 | 235 | 236 | subTests : Test 237 | subTests = 238 | describe "subtraction" 239 | [ fuzz (tuple ( integer, integer )) "x - y = x + -y" <| 240 | \( x, y ) -> 241 | Expect.equal (sub x y) (add x (BigInt.negate y)) 242 | , fuzz (tuple ( integer, integer )) "a - b = -(b - a)" <| 243 | \( a, b ) -> Expect.equal (sub a b) (BigInt.negate (sub b a)) 244 | ] 245 | 246 | 247 | mulTests : Test 248 | mulTests = 249 | describe "Mul testsuite" 250 | [ fuzz (tuple ( smallInt, smallInt )) "mult x y = x * y for small numbers" <| 251 | \( x, y ) -> 252 | mul (fromInt x) (fromInt y) 253 | |> Expect.equal (fromInt (x * y)) 254 | , fuzz (tuple ( integer, nonZeroInteger )) "(x * y) / y = x" <| 255 | \( x, y ) -> 256 | mul x y 257 | |> (\n -> divmod n y) 258 | |> Expect.equal (Just ( x, zero )) 259 | , fuzz (tuple ( integer, integer )) "x * y = y * x" <| 260 | \( x, y ) -> 261 | Expect.equal (mul x y) (mul y x) 262 | ] 263 | 264 | 265 | divmodTests : Test 266 | divmodTests = 267 | describe "divmod" 268 | [ fuzz (tuple ( integer, nonZeroInteger )) "definition" <| 269 | \( x, y ) -> 270 | case divmod x y of 271 | Nothing -> 272 | Expect.equal y (fromInt 0) 273 | 274 | Just ( c, r ) -> 275 | mul c y 276 | |> add r 277 | |> Expect.equal x 278 | ] 279 | 280 | 281 | absTests : Test 282 | absTests = 283 | describe "abs" 284 | [ fuzz integer "|x| = x; x >= 0 and |x| = -x; x < 0" <| 285 | \x -> 286 | if gte x zero then 287 | Expect.equal (BigInt.abs x) x 288 | 289 | else 290 | Expect.equal (BigInt.abs x) (BigInt.negate x) 291 | ] 292 | 293 | 294 | stringTests : Test 295 | stringTests = 296 | describe "toString and fromString" 297 | [ fuzz integer "fromString (toString x) = Just x" <| 298 | \x -> 299 | fromIntString (BigInt.toString x) 300 | |> Expect.equal (Just x) 301 | , fuzz smallInt "match string formatting from core" <| 302 | \x -> 303 | BigInt.toString (fromInt x) 304 | |> Expect.equal (String.fromInt x) 305 | , fuzz integer "accept '+' at the beginning of the string" <| 306 | \x -> 307 | let 308 | y = 309 | x 310 | |> BigInt.abs 311 | |> BigInt.toString 312 | in 313 | String.cons '+' y 314 | |> fromIntString 315 | |> Expect.equal (fromIntString y) 316 | , test "Basic toHexString" <| 317 | \_ -> 318 | let 319 | fromBase16String = 320 | BigInt.fromHexString "2386f26fc10000" 321 | 322 | midLargeInt = 323 | BigInt.fromInt 100000000 324 | 325 | fromInt = 326 | mul midLargeInt midLargeInt 327 | in 328 | Expect.equal 329 | (Maybe.map BigInt.toHexString fromBase16String) 330 | (Just "2386f26fc10000") 331 | , fuzz smallPositiveIntegers "Same results as rtfeldman/elm-hex" <| 332 | \x -> 333 | BigInt.toHexString (fromInt x) 334 | |> Expect.equal (Hex.toString x) 335 | ] 336 | 337 | 338 | minTests : Test 339 | minTests = 340 | describe "min" 341 | [ fuzz (tuple ( integer, integer )) "min x y = x; x <= y and min x y = y; x > y" <| 342 | \( x, y ) -> 343 | case BigInt.compare x y of 344 | GT -> 345 | Expect.equal (BigInt.min x y) y 346 | 347 | _ -> 348 | Expect.equal (BigInt.min x y) x 349 | ] 350 | 351 | 352 | maxTests : Test 353 | maxTests = 354 | describe "max" 355 | [ fuzz (tuple ( integer, integer )) "min x y = y; x <= y and min x y = x; x > y" <| 356 | \( x, y ) -> 357 | case BigInt.compare x y of 358 | LT -> 359 | Expect.equal (BigInt.max x y) y 360 | 361 | _ -> 362 | Expect.equal (BigInt.max x y) x 363 | ] 364 | 365 | 366 | compareTests : Test 367 | compareTests = 368 | describe "compare" 369 | [ fuzz integer "x = x" <| 370 | \x -> 371 | Expect.true "apparently x /= x" (x == x) 372 | , fuzz (tuple ( integer, integer )) "x <= x + y; y >= 0" <| 373 | \( x, y ) -> 374 | Expect.true "apparently !(x <= x + y); y >= 0" 375 | (lte x (add x (BigInt.abs y))) 376 | , fuzz (tuple ( integer, integer )) "x >= x + y; y <= 0" <| 377 | \( x, y ) -> 378 | Expect.true "apparently !(x >= x + y); y <= 0" 379 | (gte x (add x (BigInt.abs y |> BigInt.negate))) 380 | , fuzz (tuple ( integer, nonZeroInteger )) "x < x + y; y > 0" <| 381 | \( x, y ) -> 382 | Expect.true "apparently !(x < x + y); y > 0" 383 | (lt x (add x (BigInt.abs y))) 384 | , fuzz (tuple ( integer, nonZeroInteger )) "x > x + y; y < 0" <| 385 | \( x, y ) -> 386 | Expect.true "apparently !(x > x + y); y < 0" 387 | (gt x (add x (BigInt.abs y |> BigInt.negate))) 388 | ] 389 | 390 | 391 | isEvenTests : Test 392 | isEvenTests = 393 | describe "isEven" 394 | [ fuzz int "the `mod 2` of a number should be 0 if it is even" <| 395 | \x -> Expect.equal (isEven (fromInt x)) (Basics.modBy 2 x == 0) 396 | ] 397 | 398 | 399 | isOddTests : Test 400 | isOddTests = 401 | describe "isOdd" 402 | [ fuzz int "the `mod 2` of a number should be 1 if it is odd" <| 403 | \x -> Expect.equal (isOdd (fromInt x)) (Basics.modBy 2 x == 1) 404 | ] 405 | 406 | 407 | powTests : Test 408 | powTests = 409 | describe "exponentiation (pow)" 410 | [ fuzz (tuple ( tinyInt, tinyPositiveInt )) "pow x y = y ^ x for small numbers" <| 411 | \( base, exp ) -> 412 | BigInt.toString (pow (fromInt base) (fromInt exp)) 413 | |> Expect.equal (BigInt.toString (fromInt (base ^ exp))) 414 | ] 415 | --------------------------------------------------------------------------------