faster_prime 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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +5 -0
- data/README.md +68 -0
- data/Rakefile +10 -0
- data/faster_prime.gemspec +24 -0
- data/lib/faster_prime.rb +45 -0
- data/lib/faster_prime/core_ext.rb +17 -0
- data/lib/faster_prime/primality_test.rb +557 -0
- data/lib/faster_prime/prime_factorization.rb +458 -0
- data/lib/faster_prime/sieve.rb +174 -0
- data/lib/faster_prime/utils.rb +225 -0
- metadata +83 -0
@@ -0,0 +1,458 @@
|
|
1
|
+
require "faster_prime/utils"
|
2
|
+
|
3
|
+
module FasterPrime
|
4
|
+
module PrimeFactorization
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Factorize an integer
|
8
|
+
def prime_factorization(n)
|
9
|
+
return enum_for(:prime_factorization, n) unless block_given?
|
10
|
+
|
11
|
+
return if n == 0
|
12
|
+
if n < 0
|
13
|
+
yield [-1, 1]
|
14
|
+
n = -n
|
15
|
+
end
|
16
|
+
|
17
|
+
SMALL_PRIMES.each do |prime|
|
18
|
+
if n % prime == 0
|
19
|
+
c = 0
|
20
|
+
begin
|
21
|
+
n /= prime
|
22
|
+
c += 1
|
23
|
+
end while n % prime == 0
|
24
|
+
yield [prime, c]
|
25
|
+
end
|
26
|
+
if prime * prime > n
|
27
|
+
yield [n, 1] if n > 1
|
28
|
+
return
|
29
|
+
end
|
30
|
+
return if n == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
if PrimalityTest.prime?(n)
|
34
|
+
yield [n, 1]
|
35
|
+
else
|
36
|
+
d = nil
|
37
|
+
until d
|
38
|
+
[PollardRho, MPQS].each do |algo|
|
39
|
+
begin
|
40
|
+
d = algo.try_find_factor(n)
|
41
|
+
rescue Failed
|
42
|
+
else
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
pe = Hash.new(0)
|
49
|
+
prime_factorization(n / d) {|p, e| pe[p] += e }
|
50
|
+
prime_factorization(d) {|p, e| pe[p] += e }
|
51
|
+
pe.keys.sort.each do |p|
|
52
|
+
yield [p, pe[p]]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Failed < StandardError
|
59
|
+
end
|
60
|
+
|
61
|
+
# An implementation of Pollard's rho algorithm (Brent's variant)
|
62
|
+
#
|
63
|
+
# https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm#Variants
|
64
|
+
module PollardRho
|
65
|
+
module_function
|
66
|
+
|
67
|
+
def self.try_find_factor(n)
|
68
|
+
x0 = rand(n)
|
69
|
+
y = x0
|
70
|
+
m = Math.log(n, 2).ceil
|
71
|
+
c = 2
|
72
|
+
r = q = 1
|
73
|
+
while true
|
74
|
+
x = y
|
75
|
+
r.times { y = (y * y + c) % n }
|
76
|
+
k = 0
|
77
|
+
while true
|
78
|
+
ys = y
|
79
|
+
[m, r - k].min.times do
|
80
|
+
y = (y * y + c) % n
|
81
|
+
q = (q * (x - y)) % n
|
82
|
+
end
|
83
|
+
g = q.gcd(n)
|
84
|
+
k += m
|
85
|
+
break if k >= r || g > 1
|
86
|
+
end
|
87
|
+
r *= 2
|
88
|
+
break if g > 1
|
89
|
+
end
|
90
|
+
if g == n
|
91
|
+
while true
|
92
|
+
ys = (ys * ys + c) % n
|
93
|
+
g = (x - ys).gcd(n)
|
94
|
+
break if g > 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
if g == n
|
98
|
+
raise Failed, "failed to find a factor: #{ n }"
|
99
|
+
else
|
100
|
+
g
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# An implementation of MPQS factoring algorithm.
|
106
|
+
#
|
107
|
+
# R. D. Silverman,
|
108
|
+
# "The Multiple Polynomial Quadratic Sieve." (1987)
|
109
|
+
# http://www.ams.org/journals/mcom/1987-48-177/S0025-5718-1987-0866119-8/S0025-5718-1987-0866119-8.pdf
|
110
|
+
|
111
|
+
class MPQS
|
112
|
+
# MPQS parameters
|
113
|
+
# (N digits, factor base size, sieve interval, and large prime tolerance)
|
114
|
+
PARAMETERS = [
|
115
|
+
[24, 100, 250, 1.5 ],
|
116
|
+
[30, 200, 1_250, 1.5 ],
|
117
|
+
[36, 400, 1_250, 1.75],
|
118
|
+
[42, 900, 2_500, 2.0 ],
|
119
|
+
[48, 1200, 7_000, 2.0 ],
|
120
|
+
[54, 2000, 12_500, 2.2 ],
|
121
|
+
[60, 3000, 17_500, 2.4 ],
|
122
|
+
[66, 4500, 25_000, 2.6 ],
|
123
|
+
]
|
124
|
+
|
125
|
+
SMALL_PRIMES = [2, 3, 5, 7, 11]
|
126
|
+
|
127
|
+
# Generates a table for finding k such that kN = 1 mod 4 and that kN is
|
128
|
+
# rich in small quadratic residues, i.e., (kN | p) = 1 for all p in
|
129
|
+
# SMALL_PRIMES.
|
130
|
+
def self.k_table
|
131
|
+
return @@k_table if defined?(@@k_table)
|
132
|
+
|
133
|
+
k_table = { 1 => {}, 3 => {} }
|
134
|
+
Sieve.each do |p|
|
135
|
+
next if p <= SMALL_PRIMES.last
|
136
|
+
state = SMALL_PRIMES.map {|q| Utils.kronecker(p, q) }
|
137
|
+
k_table[p % 4][state] ||= p
|
138
|
+
break if k_table.all? {|r, t| t.size == 2 ** SMALL_PRIMES.size }
|
139
|
+
end
|
140
|
+
|
141
|
+
@@k_table = k_table
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.try_find_factor(n)
|
145
|
+
new(n).try_find_factor
|
146
|
+
end
|
147
|
+
|
148
|
+
S = 65536
|
149
|
+
|
150
|
+
def initialize(n)
|
151
|
+
@n = n
|
152
|
+
|
153
|
+
if @n <= 1 || PrimalityTest.prime?(@n)
|
154
|
+
@trivial_factor = 1
|
155
|
+
return
|
156
|
+
end
|
157
|
+
SMALL_PRIMES.each do |p|
|
158
|
+
if @n % p == 0
|
159
|
+
@trivial_factor = p
|
160
|
+
return
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
m = Utils.integer_square_root(n)
|
165
|
+
if m * m == n
|
166
|
+
@trivial_factor = m
|
167
|
+
return
|
168
|
+
end
|
169
|
+
|
170
|
+
# select parameters
|
171
|
+
# @k: a multiplier
|
172
|
+
@k = MPQS.k_table[@n % 4][SMALL_PRIMES.map {|q| Utils.kronecker(@n, q) }]
|
173
|
+
@kn = @k * @n
|
174
|
+
|
175
|
+
# @f: the size of the factor base
|
176
|
+
# @m: the length of the sieve interval (2M+1)
|
177
|
+
# @t: the large prime tolerance
|
178
|
+
_digit, @f, @m, @t =
|
179
|
+
PARAMETERS.find {|d,| Math.log(@n, 10) < d } || PARAMETERS.last
|
180
|
+
|
181
|
+
# compute factor base
|
182
|
+
# @ps: the factor base
|
183
|
+
# @log_ps: log of each factor base
|
184
|
+
# @sqrts: sqrt(kN) mod p
|
185
|
+
@ps, @log_ps, @sqrts = [], [], []
|
186
|
+
Sieve.each do |p|
|
187
|
+
if @n % p == 0
|
188
|
+
@trivial_factor = p
|
189
|
+
return
|
190
|
+
end
|
191
|
+
if p >= 3 && Utils.kronecker(@kn, p) == 1
|
192
|
+
@ps << p
|
193
|
+
@log_ps << (Math.log(p) * S).floor
|
194
|
+
@sqrts << Utils.mod_sqrt(@kn, p)
|
195
|
+
break if @ps.size >= @f
|
196
|
+
end
|
197
|
+
end
|
198
|
+
@pmax = @ps.last # max factor base
|
199
|
+
|
200
|
+
# for enumerating coefficients
|
201
|
+
@base_d = Math.sqrt(Math.sqrt(@kn) / (Math.sqrt(2) * @m)).floor | 3
|
202
|
+
@diff_d = 0 # 0, 4, -4, 8, -8, 12, -12, ...
|
203
|
+
|
204
|
+
# sieve table
|
205
|
+
@ws = [0] * (2 * @m + 1)
|
206
|
+
|
207
|
+
# a test value for sieve
|
208
|
+
@test_value = (Math.log(@m * Math.sqrt(@kn) / @pmax ** @t) * S).round
|
209
|
+
|
210
|
+
# relations found
|
211
|
+
@incomplete_relations = {}
|
212
|
+
|
213
|
+
# for gaussian elimination
|
214
|
+
@elim = GaussianElimination.new(@ps.size + 2)
|
215
|
+
end
|
216
|
+
|
217
|
+
# try to find any factor (not necessarily a prime)
|
218
|
+
def try_find_factor
|
219
|
+
return @trivial_factor if defined?(@trivial_factor)
|
220
|
+
|
221
|
+
catch(:factor_found) do
|
222
|
+
while true
|
223
|
+
p :foo
|
224
|
+
select_q
|
225
|
+
solve_roots
|
226
|
+
sieve
|
227
|
+
@ws.each_with_index do |w, i|
|
228
|
+
check_relation(i - @m) if w > @test_value
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Selects Q(x) = Ax^2 + Bx + C for quadratic sieve
|
235
|
+
def select_q
|
236
|
+
# find a prime D such that (kN | D) = 1 and D = 3 mod 4
|
237
|
+
begin
|
238
|
+
@d = @base_d + @diff_d
|
239
|
+
@diff_d = @diff_d > 0 ? -@diff_d : 4 - @diff_d
|
240
|
+
end until Utils.kronecker(@kn, @d) == 1 && PrimalityTest.probable_prime?(@d) != false && !@ps.include?(@d)
|
241
|
+
|
242
|
+
# select coefficients (see the paper)
|
243
|
+
@a = @d * @d
|
244
|
+
|
245
|
+
h0 = Utils.mod_pow(@kn, @d / 4, @d)
|
246
|
+
h1 = (@kn * h0) % @d
|
247
|
+
# assert: h1*h1 = kN mod D
|
248
|
+
# assert: h0*h1 = 1 mod D
|
249
|
+
h2 = (Utils.mod_inv(2, @d) * h0 * ((@kn - h1 * h1) / @d)) % @d
|
250
|
+
# assert: kN - h1^2 = 0 mod D
|
251
|
+
|
252
|
+
@b = (h1 + h2 * @d) % @a
|
253
|
+
@b -= @a if @b[0] == 0
|
254
|
+
# assert: B^2 = kN mod 4A
|
255
|
+
|
256
|
+
#@c = (@b * @b - @kn) / (4 * @a) # not needed
|
257
|
+
|
258
|
+
# compute real roots of Q(x) = 0
|
259
|
+
s = Utils.integer_square_root(@kn)
|
260
|
+
@real_root_1 = (-@b - s) / (2 * @a)
|
261
|
+
@real_root_2 = (-@b + s) / (2 * @a)
|
262
|
+
# x is between real roots if @real_root_1 <= x && x <= @real_root_2
|
263
|
+
end
|
264
|
+
|
265
|
+
# Computes the roots of Q(x) = 0 mod p (p in the factor base)
|
266
|
+
def solve_roots
|
267
|
+
@roots = []
|
268
|
+
@ps.zip(@sqrts) do |p, s|
|
269
|
+
a_inv = Utils.mod_inv(2 * @a, p)
|
270
|
+
x1 = (-@b + s) * a_inv % p
|
271
|
+
x2 = (-@b - s) * a_inv % p
|
272
|
+
@roots << [x1, x2]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Performs the quadratic sieve
|
277
|
+
def sieve
|
278
|
+
@ws.fill(0)
|
279
|
+
@ps.zip(@log_ps, @roots) do |p, lp, (s1, s2)|
|
280
|
+
((@m + s1) % p).step(2 * @m, p) {|i| @ws[i] += lp }
|
281
|
+
((@m + s2) % p).step(2 * @m, p) {|j| @ws[j] += lp }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Tests whether Q(x) is actually a product of factor bases
|
286
|
+
def check_relation(x)
|
287
|
+
h, qx = compute_q(x)
|
288
|
+
return if qx == 0
|
289
|
+
es, l = exponent_bitvector(qx)
|
290
|
+
|
291
|
+
# discard this x if the residue L is too big
|
292
|
+
return if l > @pmax ** @t
|
293
|
+
|
294
|
+
if l == 1
|
295
|
+
# complete relation found:
|
296
|
+
# Q(x) = p0^e0 * p1^e1 * ... * pk^ek (pi in the factor base)
|
297
|
+
qx_vec = Hash.new(0)
|
298
|
+
PrimeFactorization.prime_factorization(qx) {|p, e| qx_vec[p] += e }
|
299
|
+
collect_relation(es, h, qx_vec)
|
300
|
+
|
301
|
+
elsif @incomplete_relations[l]
|
302
|
+
# large prime procedure:
|
303
|
+
# make a complete relation by multiplying two incomplete relations
|
304
|
+
es2, h2, qx2 = @incomplete_relations[l]
|
305
|
+
|
306
|
+
# XXX: use FactoredInteger
|
307
|
+
qx_vec = Hash.new(0)
|
308
|
+
PrimeFactorization.prime_factorization(qx) {|p, e| qx_vec[p] += e }
|
309
|
+
PrimeFactorization.prime_factorization(qx2) {|p, e| qx_vec[p] += e }
|
310
|
+
|
311
|
+
collect_relation(es ^ es2, h * h2 % @kn, qx_vec)
|
312
|
+
|
313
|
+
else
|
314
|
+
@incomplete_relations[l] = [es, h, qx]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Adds a complete relation found to gaussian elimination list
|
319
|
+
def collect_relation(es, h, q)
|
320
|
+
#p "%0#{ @ps.size + 1 }b" % es
|
321
|
+
@elim[es] = [h, q]
|
322
|
+
|
323
|
+
if @elim.size > 0.9 * @f && @elim.size % [1, @f / 100].max == 0
|
324
|
+
@elim.eliminate do |relations|
|
325
|
+
# factor candidate found
|
326
|
+
check_factor_candidate(relations)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
if @elim.size > @f * 1.5
|
331
|
+
raise Failed, "failed to find a factor: #{ @n }"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Computes and checks a factor candidate
|
336
|
+
def check_factor_candidate(relations)
|
337
|
+
# computes the factor candidate
|
338
|
+
p1 = 1
|
339
|
+
p2_vec = Hash.new(0) # XXX: use FactoredInteger
|
340
|
+
relations.each do |h, qx_vec|
|
341
|
+
p1 = p1 * h % @kn
|
342
|
+
qx_vec.each {|p, e| p2_vec[p] += e }
|
343
|
+
end
|
344
|
+
p2 = 1
|
345
|
+
p2_vec.each {|p, e| p2 = (p2 * Utils.mod_pow(p, e / 2, @kn)) % @kn }
|
346
|
+
|
347
|
+
return if p1 == p2 || (p1 + p2) % @kn == 0
|
348
|
+
|
349
|
+
factor = (p1 + p2).gcd(@n)
|
350
|
+
|
351
|
+
# check the factor candidate
|
352
|
+
if factor != 1 && factor != @n
|
353
|
+
throw :factor_found, factor
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Computes Q(x)
|
358
|
+
def compute_q(x)
|
359
|
+
h = (2 * @a * x + @b) * Utils.mod_inv(2 * @d, @kn) % @kn
|
360
|
+
|
361
|
+
q = h * h % @kn
|
362
|
+
q -= @kn if @real_root_1 <= x && x <= @real_root_2
|
363
|
+
# assert: q == @a*x*x + @b*x + @c
|
364
|
+
|
365
|
+
return h, q
|
366
|
+
end
|
367
|
+
|
368
|
+
# Factorizes n in factor base and returns an exponent bitvector
|
369
|
+
def exponent_bitvector(n)
|
370
|
+
vec = 0
|
371
|
+
if n < 0
|
372
|
+
vec = 1
|
373
|
+
n = -n
|
374
|
+
end
|
375
|
+
bit = 2
|
376
|
+
([2] + @ps).each do |p|
|
377
|
+
e = false
|
378
|
+
while n % p == 0
|
379
|
+
n /= p
|
380
|
+
e = !e
|
381
|
+
end
|
382
|
+
vec |= bit if e
|
383
|
+
bit <<= 1
|
384
|
+
end
|
385
|
+
return vec, n
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
# Incremental gaussian elimination.
|
390
|
+
#
|
391
|
+
# A. K. Lenstra, and M. S. Manasse,
|
392
|
+
# "Compact incremental Gaussian Elimination over Z/2Z." (1988)
|
393
|
+
# http://dl.acm.org/citation.cfm?id=896266
|
394
|
+
class GaussianElimination
|
395
|
+
def initialize(m)
|
396
|
+
@n = 0
|
397
|
+
@m = m
|
398
|
+
@d = []
|
399
|
+
@e = []
|
400
|
+
@u = []
|
401
|
+
@v = []
|
402
|
+
end
|
403
|
+
|
404
|
+
def []=(es, val)
|
405
|
+
@d << es
|
406
|
+
@e << nil
|
407
|
+
@v << val
|
408
|
+
end
|
409
|
+
|
410
|
+
def size
|
411
|
+
@d.size
|
412
|
+
end
|
413
|
+
|
414
|
+
def eliminate
|
415
|
+
n2 = @d.size
|
416
|
+
|
417
|
+
@n.times do |i|
|
418
|
+
if @u[i]
|
419
|
+
@e[@u[i]] = i
|
420
|
+
@n.upto(n2 - 1) do |j|
|
421
|
+
@d[j] ^= @d[i] if @d[j][@u[i]] == 1
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
@n.upto(n2 - 1) do |j|
|
427
|
+
found = false
|
428
|
+
|
429
|
+
@m.times do |i|
|
430
|
+
if @d[j][i] == 1 && !@e[i]
|
431
|
+
@u[j] = i
|
432
|
+
@e[@u[j]] = j
|
433
|
+
@d[j] &= ~(1 << @u[j])
|
434
|
+
(j + 1).upto(n2 - 1) do |k|
|
435
|
+
@d[k] ^= @d[j] if @d[k][@u[j]] == 1
|
436
|
+
end
|
437
|
+
found = true
|
438
|
+
break
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
if !found
|
443
|
+
# linear dependency found
|
444
|
+
@u[j] = nil
|
445
|
+
c = {}
|
446
|
+
c[j] = true
|
447
|
+
@m.times do |k|
|
448
|
+
c[@e[k]] = true if @d[j][k] == 1
|
449
|
+
end
|
450
|
+
yield c.keys.map {|k| @v[k] }
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
@n = n2
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|