number-theory 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9284bb03268cfa67e75ffef537016a2a4d8d8533
4
+ data.tar.gz: 4b8ee777a7acd1c3dad895d76b8220c400231c30
5
+ SHA512:
6
+ metadata.gz: fbde0ca1af6d1e0f089b04b7801aa8a5c7b28ff39ee3e7d18637b14327ea0dcadfad18e9b030c6c968b7504fa23b246367e33057c611ecdcb49ea272c7fde285
7
+ data.tar.gz: 0ce4ba32f0a9d869d818d9a228be60a36278bd027cdf9c0847755128efa141c1d1c25e5413e8673d678e5b3bc7bd63b9686319168daf981f47d864bad591dbab
@@ -0,0 +1,15 @@
1
+ module NumberTheory
2
+
3
+ begin
4
+ require 'narray'
5
+ rescue LoadError
6
+ raise LoadError, "You must have NArray installed"
7
+ end
8
+
9
+ # Load modules
10
+ require File.dirname(__FILE__) + '/number-theory/primes.rb'
11
+ require File.dirname(__FILE__) + '/number-theory/divisors.rb'
12
+ require File.dirname(__FILE__) + '/number-theory/utils.rb'
13
+ require File.dirname(__FILE__) + '/number-theory/congruences.rb'
14
+
15
+ end
@@ -0,0 +1,82 @@
1
+ require_relative 'divisors'
2
+
3
+ module NumberTheory
4
+
5
+ module Congruences
6
+ ##
7
+ # Returns all the incongruent solutions to ax ≡ c (mod m) in ascending order
8
+ #
9
+ # == Example
10
+ # >> Congruences.congruences_solver(893, 266, 2432) # this solves 893x ≡ 266 (mod 2432)
11
+ # => [82, 210, 338, 466, 594, 722, 850, 978, 1106, 1234, 1362, 1490, 1618, 1746, 1874, 2002, 2130, 2258, 2386]
12
+ #
13
+ def self.linear_congruences_solver(a, c, m)
14
+ return [] if a == 0 || m == 0
15
+ # Assuming both a and m are positive integers
16
+ # If negative values are passed, they will be treated as positive integers
17
+ a = 0 - a if a < 0
18
+ m = 0 - m if m < 0
19
+ arr = Divisors.extended_euclidean_algo(a, m)
20
+ gcd = arr[0]
21
+ if c % gcd != 0
22
+ [] # i.e no solution
23
+ else
24
+ first = (arr[1] * c / gcd) % m
25
+ sol = [first]
26
+ i = 1
27
+ while i < gcd do
28
+ x = first + i * m / gcd
29
+ x -= m if x >= m
30
+ sol << x
31
+ i+=1
32
+ end
33
+ sol.sort
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Returns an integer d satifying the following simultaneous congruences
39
+ # x ≡ r1 (mod m1)
40
+ # x ≡ r2 (mod m2)
41
+ # x ≡ r3 (mod m3)
42
+ # and 0 <= d < m1*m2*m3
43
+ # == Example
44
+ # >> Congruences.chinese_remainder_theorem([2, 3, 2], [3, 5, 7])
45
+ # => 23
46
+ # the above solves
47
+ # # x ≡ 2 (mod 3)
48
+ # # x ≡ 3 (mod 5)
49
+ # # x ≡ 2 (mod 7)
50
+ def self.chinese_remainder_theorem(remainders, modulos)
51
+ m1 = modulos[0].abs
52
+ m2 = modulos[1].abs
53
+ m3 = modulos[2].abs
54
+ r1 = remainders[0]
55
+ r2 = remainders[1]
56
+ r3 = remainders[2]
57
+ arr = Divisors.extended_euclidean_algo(m1, m2)
58
+ g1 = arr[0]
59
+ g2 = Divisors.euclidean_algo(m2, m3)
60
+ g3 = Divisors.euclidean_algo(m3, m1)
61
+ if g1 != 1 || g2 != 1 || g3 != 1
62
+ false
63
+ else
64
+ # x ≡ r1 (mod m1) ...... (1)
65
+ # x ≡ r2 (mod m2) ...... (2)
66
+ # x ≡ r3 (mod m3) ...... (3)
67
+ # From (1) and (2), we have m1*q1 + r1 ≡ r2 (mod m2)
68
+ # which means m1*q1 - m2*q2 = r2 - r1
69
+ # So, we can solve this linear congruence equation for x0 which equals m1*q1 + r1
70
+ # Then, we can assume x0 + m1*m2*y ≡ r3 (mod m3)
71
+ # which means m1*m2*y - m3*q3 = r3 - x0
72
+ # So, we have the following solution
73
+ q1 = arr[1] * (r2 - r1)
74
+ x0 = m1 * q1 + r1
75
+ arr2 = Divisors.extended_euclidean_algo(m1*m2, m3)
76
+ x = (arr2[1] * (r3 - x0) * m1 * m2 + x0) % (m1*m2*m3) # Make sure 0 <= x < m1*m2*m3
77
+ x
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,200 @@
1
+ require_relative 'utils'
2
+ require_relative 'primes'
3
+
4
+ module NumberTheory
5
+
6
+ module Divisors
7
+
8
+ ##
9
+ # Returns the greatest integer k such that
10
+ # d^k divides n.
11
+ #
12
+ # == Example
13
+ # >> Divisors.multiplicity(1000,5)
14
+ # => 3
15
+ #
16
+ def self.multiplicity(n, d)
17
+ return 0 if n % d != 0
18
+ m, res = n, 0
19
+ while m % d == 0
20
+ m /= d
21
+ res += 1
22
+ end
23
+ res
24
+ end
25
+
26
+ ##
27
+ # Returns the ordered list of the divisors of n (1 and n included).
28
+ #
29
+ # == Example
30
+ # >> Divisors.divisors(100)
31
+ # => [1, 2, 4, 5, 10, 20, 25, 50, 100]
32
+ #
33
+ def self.divisors(n)
34
+ factors = Primes.factor(n)
35
+ ps = factors.keys.sort!
36
+ self._divisors(0, factors, ps).sort!.uniq
37
+ end
38
+
39
+ ## Helper function for divisors
40
+ def self._divisors(n = 0, factors, ps) # :nodoc:
41
+ give = []
42
+ if n == ps.size
43
+ give << 1
44
+ else
45
+ pows = [1]
46
+ factors[ps[n]].times {pows << pows[-1]*ps[n]}
47
+ for q in self._divisors(n+1, factors, ps)
48
+ for p in pows
49
+ give << p * q
50
+ end
51
+ end
52
+ end
53
+ give
54
+ end
55
+
56
+ ##
57
+ # Return sigma_0(n), i.e. the number of divisors of n.
58
+ #
59
+ # == Example
60
+ # >> Divisors.divcount(100)
61
+ # => 9
62
+ #
63
+ def self.divcount(n)
64
+ return nil if n < 0
65
+ return 1 if n == 1
66
+ divcount = 1
67
+ Primes.factor(n).values.each {|n| divcount *= (n+1)}
68
+ divcount
69
+ end
70
+
71
+ ##
72
+ # Returns sigma_k(n), i.e. the sum of the k-th powers of
73
+ # the divisors of n.
74
+ #
75
+ # == Example
76
+ # >> Divisors.divisor_sigma(10, 2)
77
+ # => 130
78
+ #
79
+ def self.divisor_sigma(n, k)
80
+ return self.divcount(n) if k == 0
81
+ res = 0
82
+ for i in self.divisors(n)
83
+ res += i**k
84
+ end
85
+ res
86
+ end
87
+
88
+ ##
89
+ # Returns true if n is a perfect number, false otherwise.
90
+ #
91
+ # == Example
92
+ # >> Divisors.perfect?(6)
93
+ # => true
94
+ #
95
+ def self.perfect? (n)
96
+ self.divisor_sigma(n, 1) == 2*n
97
+ end
98
+
99
+
100
+ ##
101
+ # Returns the valuer of phi(n), the Euler phi function; i.e.
102
+ # the number of integers in [1..n] comprime to n.
103
+ #
104
+ # == Example
105
+ # >> Divisors.euler_phi(30)
106
+ # => 8
107
+ #
108
+ # >> Divisors.euler_phi(37)
109
+ # => 36
110
+ #
111
+ # == Algorithm
112
+ # n is first factored, then the result is computed as
113
+ # n * prod_{p} (1 - 1/p)
114
+ # where the product spans over all the prime factors of n
115
+ #
116
+ def self.euler_phi(n)
117
+ return 0 if n < 1
118
+ res = n
119
+ Primes.factor(n).keys.each do |i|
120
+ res *= i - 1
121
+ res /= i
122
+ end
123
+ res
124
+ end
125
+
126
+ ##
127
+ # Returns true if a positive integer is square free;
128
+ # returns false otherwise
129
+ #
130
+ # A positive integer 'n' is said to be square free
131
+ # if no prime factor appears more than once
132
+ # in the list of prime factors for 'n'
133
+ #
134
+ # == Example
135
+ # >> Divisors.square_free?(10)
136
+ # => true
137
+ #
138
+ # The integer 1 is a special case since it is
139
+ # both a prefect square, and square free.
140
+ #
141
+ def self.square_free?(n)
142
+ return false if n <= 0
143
+ (Primes.factor(n)).each_value { |value| return false if value >= 2 }
144
+ true
145
+ end
146
+
147
+ ##
148
+ # Returns the greatest common divisor of a and b
149
+ # == Example
150
+ # >> Divisors.euclidean_algo(20, 15) = 5
151
+ def self.euclidean_algo(a, b)
152
+ a = 0 - a if a < 0
153
+ b = 0 - b if b < 0
154
+ if a == 0 || b == 0
155
+ 0
156
+ else
157
+ r = a - (a/b)*b
158
+ while r!=0 do
159
+ a = b
160
+ b = r
161
+ r = a - (a/b)*b
162
+ end
163
+ b
164
+ end
165
+ end
166
+
167
+ ##
168
+ # Returns the greatest common divisor of a and b and the corresponding Bézout coefficients
169
+ # for ax + by = g where g is the greatest common divisor of a and b
170
+ # == Example
171
+ # >> Divisors.extended_euclidean_algo(20, 15) = [5, 1, -1]
172
+ def self.extended_euclidean_algo(a, b)
173
+ a = 0 - a if a < 0
174
+ b = 0 - b if b < 0
175
+ if a == 0 || b == 0
176
+ [0, 0, 0]
177
+ else
178
+ r = a - (a/b)*b
179
+ s0 = 1; t0 = 0
180
+ s1 = 0; t1 = 1
181
+ s2 = s0 - (a/b)*s1; t2 = t0 - (a/b)*t1
182
+ while r!=0 do
183
+ a = b
184
+ b = r
185
+ r = a - (a/b)*b
186
+ s0 = s1; t0 = t1
187
+ s1 = s2; t1 = t2
188
+ s2 = s0 - (a/b)*s1; t2 = t0 - (a/b)*t1
189
+ end
190
+ ans = []
191
+ ans << b
192
+ ans << s1
193
+ ans << t1
194
+ ans
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ end
@@ -0,0 +1,400 @@
1
+ module NumberTheory
2
+
3
+ module Primes
4
+
5
+ ##
6
+ # Returns true if n is (probably) prime, false otherwise.
7
+ # For n < 10^15 the answer is accurate; greater n values
8
+ # reported as primes have a (very) small probability of
9
+ # actually being pseudoprimes.
10
+ #
11
+ # == Example
12
+ # >> Primes.prime?(12)
13
+ # => false
14
+ #
15
+ # >> Primes.prime?(1882341361)
16
+ # => true
17
+ #
18
+ # == Algorithm
19
+ # For n < 10^9 the function just test by trial division;
20
+ # for greater n values it performs a Miller-Rabin a
21
+ # pseudoprime test.
22
+ #
23
+ # If n < 341550071728321 the test is deterministic, otherwise
24
+ # there's a probability smaller than 7 x 10^-31 that a composite
25
+ # number is reported as prime.
26
+ #
27
+ def self.prime?(n)
28
+ if n < 10**9 or n % 2 == 0
29
+ self._trial_division(n)
30
+ else
31
+ self._miller_rabin(n)
32
+ end
33
+ end
34
+
35
+ ## helper method for prime?
36
+ def self._trial_division(n)
37
+ return false if n < 2 or n == 4
38
+ return true if n == 2 or n == 3 or n == 5
39
+
40
+ mods30 = [1, 7, 11, 13, 17, 19, 23, 29]
41
+ return false if not mods30.include?(n % 30)
42
+
43
+ 3.step(n**0.5, 2) { |x| return false if n % x == 0 }
44
+ true
45
+ end
46
+
47
+ ## helper method for prime?
48
+ def self._miller_rabin(n, runs = 50) # :nodoc:
49
+ if n < 341550071728321
50
+ for a in [2, 3, 5, 7, 11, 13, 17] do
51
+ return false if self._witness(a, n)
52
+ end
53
+ true
54
+ else
55
+ runs.times do
56
+ a = 1 + Random.rand(n)
57
+ return false if self._witness(a, n)
58
+ end
59
+ true
60
+ end
61
+ end
62
+
63
+ ## helper method for _miller_rabin
64
+ def self._witness(a, n)
65
+ t = Divisors.multiplicity(n - 1, 2)
66
+ u = (n - 1) / (2**t)
67
+ x1 = Utils.mod_exp(a, u, n)
68
+ t.times do |i|
69
+ x0 = x1
70
+ x1 = (x0 * x0) % n
71
+ return true if x1 == 1 and x0 != 1 and x0 != n-1
72
+ end
73
+ return true if x1 != 1
74
+ false
75
+ end
76
+
77
+ ##
78
+ # Memoized list of primes.
79
+ #
80
+ # primes_list returns a list of the prime numbers
81
+ # between low and high (extremes included).
82
+ #
83
+ # The computation is memoized in @primes_bit_array,
84
+ # @upper_limit is the current highest prime in the list.
85
+ # Later requests are serverd either by using the memoized
86
+ # list, or extending it (if high > @upper_limit)
87
+ #
88
+ # == Example
89
+ # >> Primes.primes_list(30)
90
+ # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
91
+ #
92
+ # == Algorithm
93
+ # Primes are computed using a plain version of the Sieve
94
+ # of Eratosthenes; the method uses the bit-array structure
95
+ # implemented in the NArray gem
96
+ #
97
+ @primes_bit_array = nil
98
+ @upper_limit = 0
99
+
100
+ def self.primes_list(low = 2, high)
101
+ self._initialize_bit_array(high) if not @primes_bit_array
102
+ if high > @upper_limit
103
+ self._extend_bit_array(high)
104
+ end
105
+ primes = NArray.object(high + 1).indgen!
106
+ ret = primes[@primes_bit_array[0..high]].to_a
107
+ return ret[ret.index{ |i| i >= low}..-1 ]
108
+ end
109
+
110
+ ## extend the memoized bit array up to high
111
+ def self._extend_bit_array(high)
112
+ arr = NArray.byte(high + 1)
113
+ arr[0..@upper_limit] = @primes_bit_array[0..@upper_limit]
114
+ arr[@upper_limit+1..-1] = NArray.byte(high - @upper_limit).fill(1)[0..-1]
115
+ sq = (high**0.5).ceil
116
+ 2.upto(sq) do |i|
117
+ if arr[i] == 1
118
+ j = i*i
119
+ j += i while j < @upper_limit
120
+ while j <= high
121
+ arr[j] = 0
122
+ j += i
123
+ end
124
+ end
125
+ end
126
+ @primes_bit_array = arr
127
+ @upper_limit = high
128
+ end
129
+
130
+ ## for the first computation of the bit array
131
+ def self._initialize_bit_array (high)
132
+ arr = NArray.byte(high + 1).fill(1)
133
+ arr[0], arr[1] = 0, 0
134
+ sq = (high**0.5).ceil
135
+ 2.upto(sq) do |i|
136
+ if arr[i] == 1
137
+ j = i**2
138
+ while j <= high
139
+ arr[j] = 0
140
+ j += i
141
+ end
142
+ end
143
+ end
144
+ @primes_bit_array = arr
145
+ @upper_limit = high
146
+ end
147
+
148
+ ##
149
+ # Returns the nth prime number.
150
+ #
151
+ # == Example
152
+ # >> Primes.nthprime(10**4)
153
+ # => 104729
154
+ #
155
+ # == Algorithm
156
+ # Since it can be proved that the nth prime number is
157
+ # not greater than n * (log n + log log n)
158
+ # the method returns the nth element of
159
+ # primes_list(n * (log n + log log n))
160
+ #
161
+ def self.nthprime(n)
162
+ return [2, 3, 5, 7, 11, 13][n - 1] if n < 7
163
+ lim = n * (Math.log(n) + Math.log(Math.log(n))).to_i
164
+ self.primes_list(lim + 1)[n - 1]
165
+ end
166
+
167
+ ##
168
+ # Returns the value of primepi(n), i.e. the number
169
+ # of primes smaller than n
170
+ #
171
+ # == Example
172
+ # >> Primes.primepi(100000)
173
+ # => 9592
174
+ #
175
+ def self.primepi(n)
176
+ return 0 if n < 2
177
+ self.primes_list(n).size
178
+ end
179
+
180
+
181
+ ##
182
+ # Factors n and returns an hash containing the prime
183
+ # factors of n as keys and their respective multiplicities
184
+ # as values. If limit is passed, no factors greater
185
+ # than limit are searched, and some of the keys could be
186
+ # composite.
187
+ #
188
+ # == Example
189
+ # >> Primes.factor(1690)
190
+ # => {2=>1, 5=>1, 13=>2}
191
+ #
192
+ # >> Primes.factor(1690, 10)
193
+ # => {2=>1, 5=>1, 169=>1}
194
+ #
195
+ # >> Primes.factor(79103835773176077140539788299)
196
+ # => {3267000013=>1, 4093082899=>1, 5915587277=>1}
197
+ #
198
+ # == Algorithm
199
+ # The procedure first searches for small factors of n
200
+ # in the primes list provided by the primes_list method.
201
+ # Then, if needed, the procedure switches between Pollard's Rho method
202
+ # and Pollard's p - 1 method until n is fully factored.
203
+ #
204
+ def self.factor(n, opts = {})
205
+ defaults = { limit: nil, use_rho: true, use_pm1: true}
206
+ options = defaults.merge!(opts)
207
+
208
+ limit = options[:limit]
209
+ use_rho = options[:use_rho]
210
+ use_pm1 = options[:use_pm1]
211
+
212
+ return nil if n < 1
213
+ return {1 => 1} if n == 1
214
+ return {n => 1} if self.prime?(n)
215
+
216
+ if limit
217
+ factors, m = self._trial(n, limit)
218
+ return factors if m == 1
219
+ return factors.merge({m => 1})
220
+ else
221
+ factors, m = self._trial(n, 100000)
222
+ return factors if m == 1
223
+ return factors.merge({m => 1}) if self.prime?(m)
224
+
225
+ if not (use_pm1 or use_rho)
226
+ return factors.merge({m => 1})
227
+ end
228
+
229
+ count = 1
230
+ while m > 1
231
+
232
+ ## pollard rho
233
+ if use_rho
234
+ rounds = [10**3, 10**count].max
235
+ div = self._pollard_rho(m, rounds)
236
+ if div
237
+ if self.prime?(div)
238
+ fac = {div => 1}
239
+ else
240
+ fac = self.factor(div)
241
+ end
242
+ factors.merge!(fac) { |_, v1, v2| v1 + v2 }
243
+ m /= div
244
+ return factors.merge({m => 1}) if self.prime?(m)
245
+ end
246
+ end
247
+
248
+ ## pollard p - 1
249
+ if use_pm1
250
+ bound = [10**3, 10**count].max
251
+ div = self._pollard_pm1(m, bound)
252
+ if div
253
+ if self.prime?(div)
254
+ fac = {div => 1}
255
+ else
256
+ fac = self.factor(div)
257
+ end
258
+ factors.merge!(fac) { |_, v1, v2| v1 + v2 }
259
+ m /= div
260
+ return factors.merge({m => 1}) if self.prime?(m)
261
+ end
262
+ end
263
+
264
+ count += 1
265
+ end
266
+ return factors
267
+ end
268
+ end
269
+
270
+ ## search for small factor of n (upto lim)
271
+ def self._trial(n, lim)
272
+ factors = {}
273
+ for p in self.primes_list(lim)
274
+ if n % p == 0
275
+ t = Divisors.multiplicity(n, p)
276
+ factors[p] = t
277
+ n /= p**t
278
+ end
279
+ end
280
+ return factors, n
281
+ end
282
+
283
+ ## Pollard's rho method
284
+ def self._pollard_rho(n, max_rounds, retries = 5)
285
+ v, a, i = 2, -1, 0
286
+ retries.times do
287
+ u, f = v, ->(x) { (x*x + a) % n }
288
+ max_rounds.times do
289
+ u, v = f.call(u), f.call(f.call(v))
290
+ g = n.gcd(u - v)
291
+ next if g == 1
292
+ break if g == n
293
+ return g
294
+ end
295
+ v, a = rand(n - 1), 1 + rand(n - 4)
296
+ end
297
+ return nil
298
+ end
299
+
300
+ ## Pollard's p - 1 method
301
+ def self._pollard_pm1 (n, bound, max_rounds = 8)
302
+ primes = self.primes_list(bound)
303
+ max_rounds.times do
304
+ c = 2
305
+ primes.each do |p|
306
+ Math.log(bound, p).floor.times { c = Utils.mod_exp(c, p, n) }
307
+ end
308
+ g = n.gcd(c-1)
309
+ return g if g != 1 and g != n
310
+ end
311
+ return nil
312
+ end
313
+
314
+ ##
315
+ # Returns the smallest prime number greater than n.
316
+ #
317
+ # == Example
318
+ # >> Primes.nextprime(1000)
319
+ # => 1009
320
+ #
321
+ # == Algorithm
322
+ # Starting from n, tests primality for each successive number
323
+ # equals to 1 or 5 (mod 6). Return the first prime
324
+ # found.
325
+ #
326
+ def self.nextprime(n)
327
+ if n < 10007
328
+ return self.primes_list(10007).find { |x| x > n }
329
+ else
330
+ p = n + 1
331
+ p += 1 while p % 6 != 1 and p % 6 != 5
332
+ flip = (p % 6 == 1 ? 4 : 2)
333
+ while not self.prime?(p)
334
+ p += flip
335
+ flip = 6 - flip
336
+ end
337
+ return p
338
+ end
339
+ end
340
+
341
+ ##
342
+ # Returns the greatest prime number smaller than +n+.
343
+ #
344
+ # == Example
345
+ # >> Primes.prevprime(1000)
346
+ # => 997
347
+ #
348
+ # == Algorithm
349
+ # Starting from n, tests primality for each previous number
350
+ # equals to 1 or 5 (mod 6). Return the first prime
351
+ # found.
352
+ def self.prevprime(n)
353
+ return nil if n < 3
354
+ p = n-1
355
+ p -= 1 while p % 6 != 1 and p % 6 != 5
356
+ flip = (p % 6 == 1 ? 2 : 4)
357
+ while not self.prime?(p)
358
+ p -= flip
359
+ flip = 6 - flip
360
+ end
361
+ return p
362
+ end
363
+
364
+ ##
365
+ # Returns a random prime between low and high;
366
+ # and nil if there's not one.
367
+ #
368
+ # == Example
369
+ # >> Primes.randprime(1000)
370
+ # => 631
371
+ #
372
+ # >> Primes.randprime(10000, 20000)
373
+ # => 15569
374
+ #
375
+ def self.randprime(low = 1, high)
376
+ p = self.nextprime(low + rand(high - low + 1))
377
+ p = self.prevprime(p) if p > high
378
+ return nil if p < low
379
+ return p
380
+ end
381
+
382
+ ##
383
+ # Returns the primorial of n, i.e. the product
384
+ # of the first n prime numbers.
385
+ #
386
+ # == Example
387
+ # >> Primes.primorial(10)
388
+ # => 6469693230
389
+ #
390
+ def self.primorial(n)
391
+ return nil if n < 0
392
+ return [2, 6, 30, 210, 2310][n - 1] if n < 7
393
+ upp = n * (Math.log(n) + Math.log(Math.log(n)))
394
+ primes = self.primes_list(upp)[0..n-1]
395
+ primes.inject {|a, b| a * b}
396
+ end
397
+
398
+ end
399
+
400
+ end
@@ -0,0 +1,68 @@
1
+ module NumberTheory
2
+
3
+ def self.time_once
4
+ start = Time.now
5
+ yield
6
+ (Time.now - start).round(3)
7
+ end
8
+
9
+ module Utils
10
+
11
+ ##
12
+ # Returns a^b (mod m). Negative exponents are allowed.
13
+ #
14
+ # == Example
15
+ # >> Utils.mod_exp(12, 34, 107)
16
+ # => 61
17
+ #
18
+ # == Algorithm
19
+ #
20
+ #
21
+ #
22
+ def self.mod_exp(a, b, m)
23
+ if b >= 0
24
+ res = 1
25
+ while b > 0
26
+ res = (res * a) % m if b & 1 != 0
27
+ b >>= 1
28
+ a = (a * a) % m
29
+ end
30
+ res
31
+ else
32
+ self.mod_exp(self.mod_inv(a, m), -b, m)
33
+ end
34
+ end
35
+
36
+ ##
37
+ # Returns the modular inverse of a, i.e. the number b
38
+ # such that a * b = 1 (mod m)
39
+ #
40
+ # == Example
41
+ # >> Utils.mod_inv(121, 107)
42
+ # => 23
43
+ #
44
+ # == Algorithm
45
+ #
46
+ #
47
+ #
48
+ #
49
+ def self.mod_inv(a, m)
50
+ return 0 if a % m == 0
51
+ g, a, y = self._eca(a, m)
52
+ return g != 1 ? 0 : a % m
53
+ end
54
+
55
+ ## helper function for mod_inv
56
+ def self._eca(a, b)
57
+ return b, 0, 1 if a == 0
58
+ g, y, x = self._eca(b % a, a)
59
+ return g, x - y * (b / a).floor, y
60
+ end
61
+
62
+
63
+ end
64
+
65
+
66
+ end
67
+
68
+
@@ -0,0 +1,25 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'number-theory'))
3
+
4
+ include NumberTheory
5
+
6
+ class TestCongruences < Test::Unit::TestCase
7
+ def test_linear_congruences_solver
8
+ # test linear_congruences_solver(a, c, m) which solves ax ≡ c (mod m)
9
+ assert_equal [], Congruences.linear_congruences_solver(3, 5, 6)
10
+ assert_equal [], Congruences.linear_congruences_solver(0, 5, 6)
11
+ assert_equal [1], Congruences.linear_congruences_solver(3, -5, 8)
12
+ p Congruences.linear_congruences_solver(-3, -5, -8)
13
+ assert_equal [1], Congruences.linear_congruences_solver(-3, -5, -8) # treating both a and m are positive integers
14
+ assert_equal 1078, Congruences.linear_congruences_solver(316258250, 2156, 1160718174).size
15
+ assert_equal [82, 210, 338, 466, 594, 722, 850, 978, 1106, 1234, 1362, 1490, 1618, 1746, 1874, 2002, 2130, 2258, 2386], Congruences.linear_congruences_solver(893, 266, 2432)
16
+ end
17
+
18
+ def test_chinese_remainder_theorem
19
+ assert_equal 23, Congruences.chinese_remainder_theorem([2, 3, 2], [3, 5, 7])
20
+ assert_equal false, Congruences.chinese_remainder_theorem([2, 3, 2], [3, 6, 7]) # as gcd(3, 6) = 2 which is not 1
21
+ assert_equal 1000, Congruences.chinese_remainder_theorem([10, 4, 12], [11, 12, 13])
22
+ assert_equal 8083600090651983754, Congruences.chinese_remainder_theorem([10, 4, 12], [1160718174, 1160718175, 13])
23
+ assert_equal 6725167681631185062030019654, Congruences.chinese_remainder_theorem([10, 4, 12], [2160718174, 2160718175, 2160718177])
24
+ end
25
+ end
@@ -0,0 +1,80 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'number-theory'))
3
+
4
+ include NumberTheory
5
+
6
+ class TestDivisors < Test::Unit::TestCase
7
+
8
+ def test_divisors
9
+ assert_equal(Divisors.divisors(32), [1, 2, 4, 8, 16, 32])
10
+ assert_equal(Divisors.divisors(210), [1, 2, 3, 5, 6, 7, 10, 14, 15, 21, 30, 35, 42, 70, 105, 210])
11
+ end
12
+
13
+ def test_divcount
14
+ assert_equal(Divisors.divcount(210), 16)
15
+ assert_equal(Divisors.divcount(32000), 36)
16
+ assert_equal(Divisors.divcount(3861000), 256)
17
+ assert_equal(Divisors.divcount(4900265546530028103000), 12288)
18
+ end
19
+
20
+ def test_multiplicity
21
+ assert_equal(Divisors.multiplicity(18, 2), 1)
22
+ assert_equal(Divisors.multiplicity(34992, 3), 7)
23
+ assert_equal(Divisors.multiplicity(8152454278732958496, 19), 11)
24
+ end
25
+
26
+ def test_divisor_sigma
27
+ assert_equal(Divisors.divisor_sigma(16,1), 31)
28
+ assert_equal(Divisors.divisor_sigma(20,2), 546)
29
+ assert_equal(Divisors.divisor_sigma(28,5), 17766056)
30
+ assert_equal(Divisors.divisor_sigma(15,7), 170939688)
31
+ end
32
+
33
+ def test_euler_phi
34
+ assert_equal(Divisors.euler_phi(12), 4)
35
+ assert_equal(Divisors.euler_phi(19), 18)
36
+ assert_equal(Divisors.euler_phi(25555), 19296)
37
+ assert_equal(Divisors.euler_phi(87654), 25032)
38
+ end
39
+
40
+ def test_perfect?
41
+ assert(Divisors.perfect?(6))
42
+ assert(Divisors.perfect?(496))
43
+ assert(Divisors.perfect?(2305843008139952128))
44
+ end
45
+
46
+ def test_square_free?
47
+ assert(Divisors.square_free?(1))
48
+ assert(Divisors.square_free?(110))
49
+ assert(Divisors.square_free?(3226340895))
50
+ assert(Divisors.square_free?(3226340897))
51
+ assert(!Divisors.square_free?(3226340896))
52
+ assert(!Divisors.square_free?(18))
53
+ assert(!Divisors.square_free?(0))
54
+ assert(!Divisors.square_free?(-110))
55
+ end
56
+
57
+ def test_euclidean_algo
58
+ assert_equal 1, Divisors.euclidean_algo(1, 2) # Trivial case
59
+ assert_equal 5, Divisors.euclidean_algo(20, 15) # Trivial case
60
+ assert_equal 13, Divisors.euclidean_algo(13, 13) # a = b
61
+ assert_equal 100, Divisors.euclidean_algo(1000, 100) # one is a mulitple of the other
62
+ assert_equal 0, Divisors.euclidean_algo(1000, 0) # one number is zero
63
+ assert_equal 1, Divisors.euclidean_algo(-3, -7) # treat -ve numbers as +ve ones
64
+ assert_equal 1, Divisors.euclidean_algo(99991, 200) # with a prime number
65
+ assert_equal 1078, Divisors.euclidean_algo(1160718174, 316258250)
66
+ end
67
+
68
+ def test_extended_euclidean_algo
69
+ assert_equal [1, 1, 0], Divisors.extended_euclidean_algo(1, 2) # Trivial case
70
+ assert_equal [5, 1, -1], Divisors.extended_euclidean_algo(20, 15) # Trivial case
71
+ assert_equal [13, 0, 1], Divisors.extended_euclidean_algo(13, 13) # a = b
72
+ assert_equal [100, 0, 1], Divisors.extended_euclidean_algo(1000, 100) # one is a mulitple of the other
73
+ assert_equal [0, 0, 0], Divisors.extended_euclidean_algo(1000, 0) # one number is zero
74
+ assert_equal [1, -2, 1], Divisors.extended_euclidean_algo(-3, -7) # treat -ve numbers as +ve ones
75
+ assert_equal [1, -89, 44496], Divisors.extended_euclidean_algo(99991, 200) # with a prime number
76
+ assert_equal [1078, 144397, -529960], Divisors.extended_euclidean_algo(1160718174, 316258250)
77
+ assert_equal [1078, -529960, 144397], Divisors.extended_euclidean_algo(316258250, 1160718174)
78
+ end
79
+
80
+ end
@@ -0,0 +1,84 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'number-theory'))
3
+
4
+ include NumberTheory
5
+
6
+ class TestPrimes < Test::Unit::TestCase
7
+
8
+ def test__trial_division
9
+ assert(!Primes._trial_division(1))
10
+ assert(Primes._trial_division(2))
11
+ assert(!Primes._trial_division(24680))
12
+ assert(Primes._trial_division(982451653))
13
+ assert(Primes._trial_division(1882341361))
14
+ end
15
+
16
+ def test__miller_rabin
17
+ assert(!Primes._miller_rabin(24680))
18
+ assert(Primes._miller_rabin(982451653))
19
+ assert(Primes._miller_rabin(1882341361))
20
+ assert(Primes._miller_rabin(9007199254740881))
21
+ assert(Primes._miller_rabin(9007199254740881))
22
+ end
23
+
24
+ def test_prime?
25
+ assert(!Primes.prime?(1))
26
+ assert(Primes.prime?(2))
27
+ assert(!Primes.prime?(24680))
28
+ assert(Primes.prime?(982451653))
29
+ assert(Primes.prime?(1882341361))
30
+ assert(Primes.prime?(9007199254740881))
31
+ assert(Primes.prime?(66405897020462343733))
32
+ assert(Primes.prime?(416064700201658306196320137931))
33
+ assert(Primes.prime?(6847944682037444681162770672798288913849))
34
+ assert(Primes.prime?(95647806479275528135733781266203904794419563064407))
35
+ assert(Primes.prime?(669483106578092405936560831017556154622901950048903016651289))
36
+ assert(Primes.prime?(2367495770217142995264827948666809233066409497699870112003149352380375124855230068487109373226251983))
37
+ end
38
+
39
+ def test_primes_list
40
+ assert_equal(Primes.primes_list(10), [2, 3, 5, 7])
41
+ assert_equal(Primes.primes_list(10, 30), [11, 13, 17, 19, 23, 29])
42
+ assert_equal(Primes.primes_list(10**3,10**4).size, 1229 - 168)
43
+ assert_equal(Primes.primes_list(10**5).size, 9592)
44
+ end
45
+
46
+ def test_primepi
47
+ assert_equal(Primes.primepi(10), 4)
48
+ assert_equal(Primes.primepi(10**3), 168)
49
+ assert_equal(Primes.primepi(10**6), 78498)
50
+ end
51
+
52
+ def test_factor
53
+ assert_equal(Primes.factor(10), {2=>1, 5=>1})
54
+ assert_equal(Primes.factor(10125000), {2=>3, 3=>4, 5=>6})
55
+ assert_equal(Primes.factor(10125001), {10125001=>1})
56
+ assert_equal(Primes.factor(79103835773176077140539788299), {3267000013=>1, 4093082899=>1, 5915587277=>1})
57
+ assert_equal(Primes.factor(323424426232167763068694468589), {5915587277=>1, 54673257461630679457=>1})
58
+ assert_equal(Primes.factor(12932983746293756928584532764589230), {2=>1, 5=>1, 73=>1, 383=>1, 63564265087747=>1, 727719592270351=>1})
59
+ end
60
+
61
+ def test_nextprime
62
+ assert_equal(Primes.nextprime(5), 7)
63
+ assert_equal(Primes.nextprime(98765432100), 98765432137)
64
+ assert_equal(Primes.nextprime(9876543210000000000), 9876543210000000029)
65
+ end
66
+
67
+ def test_prevprime
68
+ assert_equal(Primes.prevprime(1234567890), 1234567811)
69
+ assert_equal(Primes.prevprime(1234567890000000000), 1234567889999999953)
70
+ end
71
+
72
+ def test_nthprimes
73
+ assert_equal(Primes.nthprime(10), 29)
74
+ assert_equal(Primes.nthprime(1000), 7919)
75
+ assert_equal(Primes.nthprime(10000), 104729)
76
+ end
77
+
78
+ def test_primorial
79
+ assert_equal(Primes.primorial(5), 2310)
80
+ assert_equal(Primes.primorial(17), 1922760350154212639070)
81
+ assert_equal(Primes.primorial(25), 2305567963945518424753102147331756070)
82
+ end
83
+
84
+ end
@@ -0,0 +1,24 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'number-theory'))
3
+
4
+ include NumberTheory
5
+
6
+ class TestUtils < Test::Unit::TestCase
7
+
8
+ def test_mod_exp
9
+ assert_equal(Utils.mod_exp(123, 0, 234), 1)
10
+ assert_equal(Utils.mod_exp(4, 13, 497), 445)
11
+ assert_equal(Utils.mod_exp(1777, 1885, 100000001), 2452785)
12
+ assert_equal(Utils.mod_exp(54321, 98765, 2147483647), 252276857)
13
+ assert_equal(Utils.mod_exp(4, -13, 497), 86)
14
+ assert_equal(Utils.mod_exp(1777, -1885, 100000001), 9159955)
15
+ assert_equal(Utils.mod_exp(54321, -98765, 2147483647), 1899303417)
16
+ end
17
+
18
+ def test_mod_inv
19
+ assert_equal(Utils.mod_inv(3, 17), 6)
20
+ assert_equal(Utils.mod_inv(116003, 1500450271), 287173581)
21
+ assert_equal(Utils.mod_inv(5915587277, 54673257461630679457), 50186303895605983691)
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: number-theory
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Alberto Donizetti
8
+ - Danny Siu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: narray
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0.6'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0.6'
28
+ description: A number theory library written in pure Ruby. Provides methods for primality
29
+ test, factoring integers, and more.
30
+ email: dannysiu392005@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/number-theory.rb
36
+ - lib/number-theory/congruences.rb
37
+ - lib/number-theory/divisors.rb
38
+ - lib/number-theory/primes.rb
39
+ - lib/number-theory/utils.rb
40
+ - test/test_congruences.rb
41
+ - test/test_divisors.rb
42
+ - test/test_primes.rb
43
+ - test/test_utils.rb
44
+ homepage: https://github.com/dannysiu392005/ruby-number-theory
45
+ licenses:
46
+ - GNU GPL 3
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.6.7
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: A Ruby Number Theory Library
68
+ test_files:
69
+ - test/test_congruences.rb
70
+ - test/test_divisors.rb
71
+ - test/test_primes.rb
72
+ - test/test_utils.rb
73
+ has_rdoc: