├── CONTRIBUTORS.md ├── .gitignore ├── src ├── number_theory.nim ├── combinatorics.nim ├── primes.nim ├── integer_math.nim ├── modular_arithmetic.nim └── factorization.nim ├── number_theory.nimble ├── README.md └── LICENSE /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | - Kickstarting the project: 2 | - Mamy Ratsimbazafy (Numforge) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | 3 | # Executables shall be put in an ignored build/ directory 4 | build/ -------------------------------------------------------------------------------- /src/number_theory.nim: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2016 Mamy Ratsimbazafy 3 | 4 | import ./integer_math, 5 | modular_arithmetic, 6 | combinatorics 7 | 8 | export integer_math, modular_arithmetic, combinatorics -------------------------------------------------------------------------------- /src/combinatorics.nim: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2016 Mamy Ratsimbazafy 3 | 4 | proc binomialCoeff*(n, k: int): int = 5 | result = 1 6 | for i in 0 ..< k: 7 | result = result * (n-i) div (i + 1) 8 | 9 | when isMainModule: 10 | assert binomialCoeff(4, 2) == 6 -------------------------------------------------------------------------------- /number_theory.nimble: -------------------------------------------------------------------------------- 1 | packageName = "number_theory" 2 | version = "0.0.1" 3 | author = "Mamy Ratsimbazafy (Numforge)" 4 | description = "Number-Theory, give integers super-powers!" 5 | license = "MIT" 6 | srcDir = "src" 7 | 8 | ### Dependencies 9 | requires "nim >= 0.17.2" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Number-Theory, give integers super-powers! 2 | 3 | Number-Theory is a package collecting the [integer library](https://github.com/mratsim/nim-project-euler) I build up to solve [Project Euler](https://projecteuler.net/) problems. 4 | 5 | At the moment it contains: 6 | - Combinatorics: binomial coefficient computation 7 | - Integer square root 8 | - Modular arithmetic 9 | - Primes algorithms: an optimised Sieve of Eratosthenes 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 the Number-Theory package contributors 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 | -------------------------------------------------------------------------------- /src/primes.nim: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2016 Mamy Ratsimbazafy 3 | 4 | import 5 | std/bitops, 6 | ./integer_math 7 | 8 | type 9 | Base = uint64 10 | ByteSeq = seq[Base] 11 | BitSeq = distinct ByteSeq 12 | 13 | const 14 | Shift = fastLog2(sizeof(Base) * 8) 15 | Mask = sizeof(Base) * 8 - 1 16 | 17 | func `[]`(bs: BitSeq, i: int): bool = 18 | bool ByteSeq(bs)[i shr Shift] shr (i and Mask) and 1 19 | 20 | func setBit(bs: var BitSeq, i: int) = 21 | template pos: untyped = ByteSeq(bs)[i shr Shift] 22 | pos = pos or uint64(1 shl (i and Mask)) 23 | 24 | # To limit initialization time, primes will be with value 0 in the bit array 25 | # Non-prime will be with value 1 26 | func primeSieve*(n: int): seq[int] = 27 | ## Sieve of Erastosthenes 28 | # Look Ma! no trial division 29 | doAssert n > 2 30 | 31 | var sieve = newSeq[uint64](n shr Shift + 1).BitSeq 32 | let max_n = (n-1) shr 1 33 | let sqr_n = isqrt(n) shr 1 34 | 35 | result = @[2] 36 | 37 | # 1. Cross-off multiples 38 | for i in 1 .. sqr_n: 39 | if not sieve[i]: 40 | let prime = i shl 1 + 1 41 | result.add prime 42 | for j in countup((prime*prime) shr 1, max_n, prime): 43 | # Cross-off multiples from i^2 to n 44 | # increment by i^2 + 2i because i^2+i is even 45 | sieve.setBit(j) 46 | 47 | # 2. Everything left in √n .. n is also a prime 48 | for i in sqr_n+1 .. max_n: 49 | if not sieve[i]: 50 | result.add(i shl 1 + 1) 51 | 52 | when isMainModule: 53 | import times 54 | 55 | echo "Warmup" 56 | discard primeSieve(1_000_000) 57 | echo "Warmup successful" 58 | 59 | let start = cpuTime() 60 | discard primeSieve(1_000_000_000) # don't forget to compile with release 61 | let stop = cpuTime() 62 | 63 | echo "Time taken: ", stop - start 64 | 65 | echo primeSieve(1_000) 66 | -------------------------------------------------------------------------------- /src/integer_math.nim: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2016 Mamy Ratsimbazafy 3 | 4 | import std/bitops 5 | 6 | proc isOdd*[T: SomeInteger](i: T): bool {.inline, noSideEffect.}= (i and 1) != 0 7 | proc isEven*[T: SomeInteger](i: T): bool {.inline, noSideEffect.}= (i and 1) == 0 8 | 9 | proc isqrt*[T: SomeInteger](n: T): T {.noSideEffect.}= 10 | ## Integer square root, return the biggest squarable number under n 11 | ## Computation via Newton method 12 | result = n 13 | var y = (2.T shl ((n.fastLog2() + 1) shr 1)) - 1 14 | while y < result: 15 | result = y 16 | y = (result + n div result) shr 1 17 | 18 | proc product*[T](x: varargs[T]): T {.noSideEffect.} = 19 | ## Computes the sum of the elements in `x`. 20 | ## If `x` is empty, 0 is returned. 21 | assert x.len > 0 22 | result = 1.T 23 | for n in x: 24 | result *= n 25 | 26 | type 27 | ldiv_t {.bycopy, importc: "ldiv_t", header:"".} = object 28 | quot{.importc: "quot".}: clong ## quotient 29 | rem{.importc: "rem".}: clong ## remainder 30 | 31 | lldiv_t {.bycopy, importc: "lldiv_t", header:"".} = object 32 | quot{.importc: "quot".}: clonglong 33 | rem{.importc: "rem".}: clonglong 34 | 35 | proc ldiv(a, b: clong): ldiv_t {.importc: "ldiv", header: "".} 36 | proc lldiv(a, b: clonglong): lldiv_t {.importc: "lldiv", header: "".} 37 | 38 | proc divmod*[T: clong or clonglong or int or int32 or int64](a, b: T): tuple[quot, rem: T] {.inline.}= 39 | ## Compute quotient and reminder of integer division in a single operation 40 | 41 | when T.sizeof == 4: # 32-bit (int32, clong) 42 | let ld = ldiv(a,b) 43 | result.quot = T(ld.quot) 44 | result.rem = T(ld.rem) 45 | elif T.sizeof == 8: # 64-bit (int64, clong) 46 | let ld = lldiv(a,b) 47 | result.quot = T(ld.quot) 48 | result.rem = T(ld.rem) 49 | 50 | proc divmod*[T: SomeUnsignedInt or int8 or int16](a, b: T): tuple[quot, rem: T] {.inline.}= 51 | # There is no single instruction for unsigned ints 52 | # Hopefully the compiler does its work properly 53 | (a div b, a mod b) 54 | -------------------------------------------------------------------------------- /src/modular_arithmetic.nim: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # Copyright (c) 2016 Mamy Ratsimbazafy 3 | 4 | from ./integer_math import isOdd, divmod 5 | 6 | proc addmod*[T: SomeInteger](a, b, m: T): T = 7 | ## Modular addition 8 | 9 | let a_m = if a < m: a 10 | else: a mod m 11 | if b == 0.T: 12 | return a_m 13 | let b_m = if b < m: b 14 | else: b mod m 15 | 16 | # We don't do a + b to avoid overflows 17 | # But we know that m at least is inferior to biggest T 18 | 19 | let b_from_m = m - b_m 20 | if a_m >= b_from_m: 21 | return a_m - b_from_m 22 | return m - b_from_m + a_m 23 | 24 | proc submod*[T: SomeInteger](a, b, m: T): T = 25 | ## Modular substraction 26 | 27 | let a_m = if a < m: a 28 | else: a mod m 29 | if b == 0.T: 30 | return a_m 31 | let b_m = if b < m: b 32 | else: b mod m 33 | 34 | # We don't do a - b to avoid overflows 35 | 36 | if a_m >= b_m: 37 | return a_m - b_m 38 | return m - b_m + a_m 39 | 40 | proc doublemod[T: SomeInteger](a, m: T): T {.inline.}= 41 | ## double a modulo m. assume a < m 42 | #result = a 43 | #if a >= m - a: 44 | # result -= m 45 | #result += a 46 | result = (a shl 1) - (if a >= (m - a): m else: 0) 47 | 48 | proc mulmod*[T: SomeInteger](a, b, m: T): T = 49 | ## Modular multiplication 50 | 51 | var a_m = a mod m 52 | var b_m = b mod m 53 | if b_m > a_m: 54 | swap(a_m, b_m) 55 | while b_m > 0.T: 56 | if b_m.isOdd: 57 | result = addmod(result, a_m, m) 58 | #a_m = doublemod(a_m, m) 59 | a_m = (a_m shl 1) - (if a_m >= (m - a): m else: 0) 60 | b_m = b_m shr 1 61 | 62 | proc expmod*[T: SomeInteger](base, exponent, m: T): T = 63 | ## Modular exponentiation 64 | 65 | # Formula from applied Cryptography by Bruce Schneier 66 | # function modular_pow(base, exponent, modulus) 67 | # result := 1 68 | # while exponent > 0 69 | # if (exponent mod 2 == 1): 70 | # result := (result * base) mod modulus 71 | # exponent := exponent >> 1 72 | # base = (base * base) mod modulus 73 | # return result 74 | 75 | result = 1.T # (exp 0 = 1) 76 | 77 | var e = exponent 78 | var b = base 79 | 80 | while e > 0.T: 81 | if isOdd e: 82 | result = mulmod(result, b, m) 83 | e = e shr 1 # e div 2 84 | b = mulmod(b,b,m) 85 | 86 | proc invmod*[T:SomeInteger](a, m: T): T = 87 | ## Modular multiplication inverse 88 | ## Input: 89 | ## - 2 positive integers a and m 90 | ## Result: 91 | ## - An integer z that solves `az ≡ 1 mod m` 92 | # Adapted from Knuth, The Art of Computer Programming, Vol2 p342 93 | # and Menezes, Handbook of Applied Cryptography (HAC), p610 94 | # to avoid requiring signed integers 95 | # http://cacr.uwaterloo.ca/hac/about/chap14.pdf 96 | 97 | # Starting from the extended GCD formula (Bezout identity), 98 | # `ax + by = gcd(x,y)` 99 | # with input x,y and outputs a, b, gcd 100 | # We assume a and m are coprimes, i.e. gcd is 1, otherwise no inverse 101 | # `ax + my = 1` 102 | # `ax + my ≡ 1 mod m` 103 | # `ax ≡ 1 mod m`` 104 | # Meaning we can use the Extended Euclid Algorithm 105 | # `ax + by` with 106 | # a = a, x = result, b = m, y = 0 107 | 108 | var 109 | a = a 110 | x = 1.T 111 | b = m 112 | y = 0.T 113 | oddIter = true # instead of requiring signed int, we keep track of even/odd iterations which would be in negative 114 | 115 | while b != 0.T: 116 | let 117 | (q, r) = divmod(a,b) 118 | t = x + q * y 119 | x = y; y = t; a = b; b = r 120 | oddIter = not oddIter 121 | 122 | if a != 1.T: 123 | # a now holds the gcd(a, m) and should equal 1 124 | raise newException(ValueError, "No modular inverse exists") 125 | 126 | if oddIter: 127 | return x 128 | return m - x 129 | 130 | # The template is replacing evrything recursively, even in addmod 131 | # We don't want that 132 | # template modulo*[T:SomeInteger](modulus: T, body: untyped): untyped = 133 | # # `+`, `*`, `**` and pow will be replaced by their modular version 134 | # template `+`(a, b: T): T = 135 | # addmod(a, b, `modulus`) 136 | # template `-`(a, b: T): T = 137 | # submod(a, b, `modulus`) 138 | # template `*`(a, b: T): T = 139 | # mulmod(a, b, `modulus`) 140 | # template `^`(a, b: T): T = 141 | # expmod(a, b, `modulus`) 142 | # template pow(a, b: T): T = 143 | # expmod(a, b, `modulus`) 144 | # body 145 | 146 | when isMainModule: 147 | # https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/fast-modular-exponentiation 148 | doAssert expmod(5, 117,19) == 1 149 | doAssert expmod(3, 1993, 17) == 14 150 | 151 | doAssert invmod(42, 2017) == 1969 152 | doAssert invmod(271, 383) == 106 # Handbook of Applied Cryptography p610 153 | 154 | doAssert expmod(5'u8, 117'u8,19'u8) == 1'u8 155 | doAssert expmod(3'u16, 1993'u16, 17'u16) == 14'u16 156 | 157 | doAssert invmod(42'u16, 2017'u16) == 1969'u16 158 | doAssert invmod(271'u16, 383'u16) == 106'u16 159 | -------------------------------------------------------------------------------- /src/factorization.nim: -------------------------------------------------------------------------------- 1 | # Impulse 2 | # Copyright (c) 2020-Present The SciNim Project 3 | # Licensed and distributed under either of 4 | # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). 5 | # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). 6 | # at your option. This file may not be copied, modified, or distributed except according to those terms. 7 | 8 | # ############################################################ 9 | # 10 | # Prime Factorization 11 | # 12 | # ############################################################ 13 | 14 | import std/bitops 15 | # Note: Compile-time precomputations 16 | 17 | func product(s: openArray[SomeInteger]): SomeInteger = 18 | if s.len == 0: return 0 19 | result = 1 20 | for num in s: 21 | result *= num 22 | 23 | func gcd(u, v: int): int = 24 | # With wheel factorization we only need to test 25 | # the numbers that are coprimes with the wheel circumference 26 | # coprime <=> gcd(n, circumference) == 1 27 | if u == 0: return v 28 | if v == 0: return u 29 | let shift = countTrailingZeroBits(u or v) 30 | var u = u shr u.countTrailingZeroBits() 31 | var v = v 32 | while true: 33 | v = v shr v.countTrailingZeroBits() 34 | if u > v: 35 | swap(u, v) 36 | v = v - u 37 | if v == 0: 38 | return u shl shift 39 | 40 | func wheel*(primes: openarray[int32]): seq[uint8] = 41 | ## Build wheel of prime gaps for wheel factorization 42 | ## https://en.wikipedia.org/wiki/Wheel_factorization 43 | 44 | # 1. Generate the coprimes of the wheel circumference 45 | let wheelCircumference = primes.product 46 | var wheelCoprimes: seq[int] 47 | for i in primes[^1]+2 ..< wheelCircumference+2: 48 | if gcd(i, wheelCircumference) == 1: 49 | wheelCoprimes.add i 50 | 51 | # 2. Compute the gaps between coprimes 52 | for i in 1 ..< wheelCoprimes.len: 53 | let gap = wheelCoPrimes[i] - wheelCoPrimes[i-1] 54 | result.add uint8(gap) 55 | 56 | let nextTurn = wheelCircumference + wheelCoprimes[0] - wheelCoprimes[^1] 57 | result.add uint8(nextTurn) 58 | 59 | iterator listFactorize*(n: var int32, primes: openarray[int32]): int32 = 60 | ## Factorize via a list of primes 61 | for prime in primes: 62 | while n mod prime == 0: 63 | yield prime 64 | n = n div prime 65 | 66 | iterator wheelFactorize*(n: int32, firstNextPrime: int32, wheel: openarray[uint8]): int32 = 67 | ## Factorize n using wheel factorization 68 | ## Assuming a 2-3-5 wheel, `firstNextPrime` should be 7 69 | ## Assuming a 2-3-5-7 wheel, `firstNextPrime` should be 11 70 | var divisor = firstNextPrime 71 | var gap = 0 72 | var n = n 73 | while divisor*divisor <= n: 74 | while n mod divisor == 0: 75 | yield divisor 76 | n = n div divisor 77 | if gap == wheel.len: 78 | gap = 0 79 | divisor += int32 wheel[gap] 80 | gap += 1 81 | if n > 1: 82 | yield n 83 | 84 | # Sanity checks 85 | # ------------------------------------------------------------------------------- 86 | 87 | when isMainModule: 88 | import std/sequtils 89 | 90 | func maxNumFactors(maxNum, minDivisorWithoutMultiples: int64): int = 91 | ## Compute the max number of factors 92 | ## that can be had for any number less or equal `maxNum` 93 | ## with `minDivisorWithoutMultiples` 94 | ## i.e. assuming we factorize with 2, 3, 4, 95 | ## the `minDivisorWithoutMultiples` is 3 96 | ## as factorizing with 4 will significantly reduce the max number of `2` factors 97 | ## This is useful to dimension the array of twiddle factors 98 | ## without dynamic memory allocation (for real-time signal processing) 99 | 100 | var d = minDivisorWithoutMultiples 101 | result = 0 102 | while d < maxNum: 103 | d *= minDivisorWithoutMultiples 104 | inc result 105 | 106 | echo "Max num factors: ", high(int32).maxNumFactors(minDivisorWithoutMultiples = 4) 107 | 108 | proc gcdChecks() = 109 | doAssert gcd(2, 3) == 1 110 | doAssert gcd(2, 5) == 1 111 | doAssert gcd(2, 12) == 2 112 | doAssert gcd(36, 14) == 2 113 | doAssert gcd(36, 28) == 4 114 | doAssert gcd(2*3*5*7*7, 5*7*7*13) == 5*7*7 115 | 116 | gcdChecks() 117 | 118 | proc wheelChecks() = 119 | doAssert wheel([int32 2, 3, 5]) == @[uint8 4, 2, 4, 2, 4, 6, 2, 6] 120 | 121 | doAssert wheel([int32 2, 3, 5, 7]) == @[ 122 | uint8 2, 4, 2, 4, 6, 2, 6, 4, 123 | 2, 4, 6, 6, 2, 6, 4, 2, 124 | 6, 4, 6, 8, 4, 2, 4, 2, 125 | 4, 8, 6, 4, 6, 2, 4, 6, 126 | 2, 6, 6, 4, 2, 4, 6, 2, 127 | 6, 4, 2, 4, 2, 10, 2, 10 128 | ] 129 | 130 | echo "wheel size 2-3-5-7-11: ", [int32 2, 3, 5, 7, 11].product, ", gaps: ", wheel([int32 2, 3, 5, 7, 11]).len 131 | 132 | wheelChecks() 133 | 134 | proc factorChecks(n: int32, firstNextPrime: static int32, primes: static seq[int32]): seq[int32] = 135 | var n = n 136 | for factor in n.listFactorize(primes): 137 | result.add factor 138 | 139 | const wheel = wheel(primes) 140 | for factor in wheelFactorize(n, firstNextPrime, wheel): 141 | result.add factor 142 | 143 | doAssert: factorChecks(12, firstNextPrime = 7, @[int32 2, 3, 5]) == @[int32 2, 2, 3] 144 | doAssert: factorChecks(24, firstNextPrime = 7, @[int32 2, 3, 5]) == @[int32 2, 2, 2, 3] 145 | doAssert: factorChecks(4817191, firstNextPrime = 7, @[int32 2, 3, 5]) == @[int32 1303, 3697] 146 | 147 | doAssert: factorChecks(12, firstNextPrime = 11, @[int32 2, 3, 5, 7]) == @[int32 2, 2, 3] 148 | doAssert: factorChecks(24, firstNextPrime = 11, @[int32 2, 3, 5, 7]) == @[int32 2, 2, 2, 3] 149 | doAssert: factorChecks(4817191, firstNextPrime = 11, @[int32 2, 3, 5, 7]) == @[int32 1303, 3697] 150 | 151 | doAssert: factorChecks(12, firstNextPrime = 13, @[int32 2, 3, 5, 7, 11]) == @[int32 2, 2, 3] 152 | doAssert: factorChecks(24, firstNextPrime = 13, @[int32 2, 3, 5, 7, 11]) == @[int32 2, 2, 2, 3] 153 | doAssert: factorChecks(4817191, firstNextPrime = 13, @[int32 2, 3, 5, 7, 11]) == @[int32 1303, 3697] --------------------------------------------------------------------------------