Skip to content

Commit 5aa2a35

Browse files
authored
Merge pull request #118 from JuliaMath/oscardssmith-eachfactor
Add `eachfactor` iterator
2 parents 2136a6a + 3457408 commit 5aa2a35

File tree

3 files changed

+109
-52
lines changed

3 files changed

+109
-52
lines changed

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DocTestSetup = :(using Primes)
77
## Prime factorization
88

99
```@docs
10+
Primes.eachfactor
1011
Primes.factor
1112
Primes.prodfactors
1213
```

src/Primes.jl

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ using Base: BitSigned
99
using Base.Checked: checked_neg
1010
using IntegerMathUtils
1111

12-
export isprime, primes, primesmask, factor, ismersenneprime, isrieselprime,
12+
export isprime, primes, primesmask, factor, eachfactor, ismersenneprime, isrieselprime,
1313
nextprime, nextprimes, prevprime, prevprimes, prime, prodfactors, radical, totient
1414

1515
include("factorization.jl")
@@ -148,8 +148,11 @@ end
148148

149149
const N_SMALL_FACTORS = 2^16
150150
const _MIN_FACTOR = UInt8.(_generate_min_factors(N_SMALL_FACTORS))
151-
# _min_factor(n) = 1 if n is prime, otherwise the minimum factor of n
152-
_min_factor(n) = _MIN_FACTOR[n>>1]
151+
# _min_factor(n) = the minimum factor of n for odd n, if 1<n<N_SMALL_FACTORS
152+
function _min_factor(n::T) where T<:Integer
153+
m = _MIN_FACTOR[n>>1]
154+
return m==1 ? n : T(m)
155+
end
153156

154157
"""
155158
isprime(n::Integer) -> Bool
@@ -168,7 +171,7 @@ function isprime(n::Integer)
168171
n < 2 && return false
169172
trailing_zeros(n) > 0 && return n==2
170173
if n < N_SMALL_FACTORS
171-
return _min_factor(n) == 1
174+
return _min_factor(n) == n
172175
end
173176
for m in (3, 5, 7, 11, 13, 17, 19, 23)
174177
n % m == 0 && return false
@@ -190,11 +193,9 @@ end
190193

191194
"""
192195
isprime(x::BigInt, [reps = 25]) -> Bool
193-
194196
Probabilistic primality test. Returns `true` if `x` is prime with high probability (pseudoprime);
195197
and `false` if `x` is composite (not prime). The false positive rate is about `0.25^reps`.
196198
`reps = 25` is considered safe for cryptographic applications (Knuth, Seminumerical Algorithms).
197-
198199
```julia
199200
julia> isprime(big(3))
200201
true
@@ -251,65 +252,103 @@ isprime(n::UInt128) =
251252
isprime(n::Int128) = n < 2 ? false :
252253
n typemax(UInt64) ? isprime(UInt64(n)) : isprime(BigInt(n))
253254

254-
# Cache of small factors for tiny numbers (n < 2^16)
255+
struct FactorIterator{T<:Integer}
256+
n::T
257+
FactorIterator(n::T) where {T} = new{T}(n)
258+
end
259+
260+
IteratorSize(::Type{<:FactorIterator}) = Base.SizeUnknown()
261+
IteratorEltype(::Type{<:FactorIterator}) = Base.HasEltype()
262+
eltype(::Type{FactorIterator{T}}) where {T} = Tuple{T, Int}
263+
Base.isempty(f::FactorIterator) = f.n == 1
264+
265+
# Iterates over the factors of n in an arbitrary order
266+
# Uses a variety of algorithms depending on the size of n to find a factor.
267+
# https://en.algorithmica.org/hpc/algorithms/factorization
268+
# Cache of small factors for small numbers (n < 2^16)
255269
# Trial division of small (< 2^16) precomputed primes
256270
# Pollard rho's algorithm with Richard P. Brent optimizations
257271
# https://en.wikipedia.org/wiki/Trial_division
258272
# https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm
259273
# http://maths-people.anu.edu.au/~brent/pub/pub051.html
260274
#
261-
function factor!(n::T, h::AbstractDict{K,Int}) where {T<:Integer,K<:Integer}
262-
# check for special cases
263-
if n < 0
264-
h[-1] = 1 - h[-1]
265-
if isa(n, BitSigned) && n == typemin(T)
266-
h[2] += 8 * sizeof(T) - 1
267-
return h
268-
else
269-
return factor!(checked_neg(n), h)
275+
276+
"""
277+
eachfactor(n::Integer)->FactorIterator
278+
Returns a lazy iterator of factors of `n` in `(factor, multiplicity)` pairs.
279+
This can be very useful for computing multiplicitive functions since for small numbers (eg numbers with no factor `>2^16`),
280+
allocating the storage required for `factor(n)` can introduce significant overhead.
281+
"""
282+
eachfactor(n::Integer) = FactorIterator(n)
283+
284+
# state[1] is the current number to factor (this decreases when factors are found)
285+
# state[2] is the prime to start trial division with.
286+
function iterate(f::FactorIterator{T}, state=(f.n, T(3))) where T
287+
n, p::T = state
288+
if n <= p
289+
n == 1 && return nothing
290+
if n < 0
291+
# if n is typemin, we can't negate it properly
292+
# instead we set p=n which we can detect here.
293+
if isa(n, BitSigned) && n == typemin(T)
294+
if n != p
295+
return (T(-1), 1), (n, n)
296+
end
297+
return (T(2), 8 * sizeof(T) - 1), T.((1, 1))
298+
end
299+
return (T(-1), 1), (-n, p)
270300
end
271-
elseif n == 0
272-
h[n] = 1
273-
return h
274-
elseif trailing_zeros(n) > 0
275-
tz = trailing_zeros(n)
276-
increment!(h, tz, 2)
277-
n >>= tz
301+
n == 0 && return (T(n), 1), (T(1), p)
278302
end
303+
tz = trailing_zeros(n)
304+
tz>0 && return (T(2), tz), (n >> tz, p)
279305
if n <= N_SMALL_FACTORS
306+
p = _min_factor(n)
307+
num_p = 1
280308
while true
281-
n == 1 && return h
282-
if _min_factor(n)==1
283-
return increment!(h, 1, n)
284-
else
285-
increment!(h, 1, _min_factor(n))
286-
n = div(n, _min_factor(n))
287-
end
309+
n = div(n, p)
310+
n == 1 && break
311+
_min_factor(n) == p || break
312+
num_p += 1
288313
end
289-
elseif isprime(n)
290-
return increment!(h, 1, n)
314+
return (p, num_p), (n, p)
315+
elseif p == 3 && isprime(n)
316+
return (n, 1), (T(1), n)
291317
end
292-
local p::T
293-
for p in 3:2:N_SMALL_FACTORS
294-
_min_factor(p) == 1 || continue
318+
for p in p:2:N_SMALL_FACTORS
319+
_min_factor(p) == p || continue
295320
num_p = 0
296321
while true
297322
q, r = divrem(n, T(p)) # T(p) so julia <1.9 uses fast divrem for `BigInt`
298323
r == 0 || break
299324
num_p += 1
300325
n = q
301326
end
302-
# h[p] += num_p (about 2x faster, but the speed only matters for small numbers)
303327
if num_p > 0
304-
increment!(h, num_p, p)
305-
# if n is small, then recursing will hit the fast path.
306-
n < N_SMALL_FACTORS && return factor!(n, h)
328+
return (p, num_p), (n, p+2)
307329
end
308330
p*p > n && break
309331
end
310-
n == 1 && return h
311-
isprime(n) && return increment!(h, 1, n)
312-
T <: BigInt || widemul(n - 1, n - 1) typemax(n) ? pollardfactors!(n, h) : pollardfactors!(widen(n), h)
332+
# if n < 2^32, then if it wasn't prime, we would have found the factors by trial division
333+
if n <= 2^32 || isprime(n)
334+
return (n, 1), (T(1), n)
335+
end
336+
should_widen = T <: BigInt || widemul(n - 1, n - 1) typemax(n)
337+
p = should_widen ? pollardfactor(n) : pollardfactor(widen(n))
338+
num_p = 0
339+
while true
340+
q, r = divrem(n, p)
341+
r != 0 && return (p, num_p), (n, p)
342+
num_p += 1
343+
n = q
344+
end
345+
end
346+
347+
function factor!(n::T, h::AbstractDict{K,Int}) where {T<:Integer,K<:Integer}
348+
for (p, num_p) in eachfactor(n)
349+
increment!(h, num_p, p)
350+
end
351+
return h
313352
end
314353

315354

@@ -425,15 +464,15 @@ julia> radical(2*2*3)
425464
6
426465
```
427466
"""
428-
radical(n) = prod(factor(Set, n))
467+
radical(n) = n==1 ? one(n) : prod(p for (p, num_p) in eachfactor(n))
429468

430-
function pollardfactors!(n::T, h::AbstractDict{K,Int}) where {T<:Integer,K<:Integer}
469+
function pollardfactor(n::T) where {T<:Integer}
431470
while true
432471
c::T = rand(1:(n - 1))
433472
G::T = 1
434-
r::K = 1
473+
r::T = 1
435474
y::T = rand(0:(n - 1))
436-
m::K = 100
475+
m::T = 100
437476
ys::T = 0
438477
q::T = 1
439478
x::T = 0
@@ -446,7 +485,7 @@ function pollardfactors!(n::T, h::AbstractDict{K,Int}) where {T<:Integer,K<:Inte
446485
y = y^2 % n
447486
y = (y + c) % n
448487
end
449-
k::K = 0
488+
k::T = 0
450489
G = 1
451490
while k < r && G == 1
452491
ys = y
@@ -467,10 +506,13 @@ function pollardfactors!(n::T, h::AbstractDict{K,Int}) where {T<:Integer,K<:Inte
467506
G = gcd(x > ys ? x - ys : ys - x, n)
468507
end
469508
if G != n
470-
isprime(G) ? h[G] = get(h, G, 0) + 1 : pollardfactors!(G, h)
471509
G2 = div(n,G)
472-
isprime(G2) ? h[G2] = get(h, G2, 0) + 1 : pollardfactors!(G2, h)
473-
return h
510+
f = min(G, G2)
511+
_gcd = gcd(G, G2)
512+
if _gcd != 1
513+
f = _gcd
514+
end
515+
return isprime(f) ? f : pollardfactor(f)
474516
end
475517
end
476518
end
@@ -569,8 +611,16 @@ positive integers less than or equal to ``n`` that are relatively prime to
569611
The totient function of `n` when `n` is negative is defined to be
570612
`totient(abs(n))`.
571613
"""
572-
function totient(n::Integer)
573-
totient(factor(abs(n)))
614+
function totient(n::T) where T<:Integer
615+
n = abs(n)
616+
if n == 0
617+
throw(ArgumentError("ϕ(0) is not defined"))
618+
end
619+
result = one(T)
620+
for (p, k) in eachfactor(n)
621+
result *= p^(k-1) * (p - 1)
622+
end
623+
result
574624
end
575625

576626
# add: checked add (when makes sense), result of same type as first argument

test/runtests.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@ end
239239
# factor returns a sorted dict
240240
@test all([issorted(collect(factor(rand(Int)))) for x in 1:100])
241241

242+
# test eachfactor iteration
243+
@test iterate(eachfactor(36)) == ((2, 2), (9, 3))
244+
@test iterate(eachfactor(7^2*5^3)) == ((5, 3), (49, 5))
245+
@test iterate(eachfactor(257)) == ((257, 1), (1, 257))
246+
@test iterate(eachfactor(nextprime(2^16))) == ((65537, 1), (1, 65537))
247+
242248
# Lucas-Lehmer
243249
@test !ismersenneprime(2047)
244250
@test ismersenneprime(8191)

0 commit comments

Comments
 (0)