Skip to content

Commit

Permalink
invmod(n::BitInteger): efficient 1-arg modular inverses
Browse files Browse the repository at this point in the history
Implement algorithm described in https://arxiv.org/pdf/2204.04342.pdf
  • Loading branch information
StefanKarpinski committed Nov 15, 2023
1 parent eaef647 commit 63c15e5
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 2 deletions.
38 changes: 37 additions & 1 deletion base/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ gcdx(a::T, b::T) where T<:Real = throw(MethodError(gcdx, (a,b)))
# multiplicative inverse of n mod m, error if none

"""
invmod(n, m)
invmod(n::Integer, m::Integer)
Take the inverse of `n` modulo `m`: `y` such that ``n y = 1 \\pmod m``,
and ``div(y,m) = 0``. This will throw an error if ``m = 0``, or if
Expand Down Expand Up @@ -257,6 +257,42 @@ function invmod(n::Integer, m::Integer)
return mod(x, m)
end

"""
invmod(n::Integer, T) where {T <: Base.BitInteger}
invmod(n::T) where {T <: Base.BitInteger}
Compute the modular inverse of `n` in the integer ring of type `T`, i.e. modulo
`2^N` where `N = 8*sizeof(T)` (e.g. `N = 32` for `Int32`). In other words these
methods satisfy the following identities:
- `(n * invmod(n, T)) % T == 1`
- `n * invmod(n) == 1`
Note that `*` here is modular multiplication in the ring that `n` belongs to.
Specifying the modulus implied by an integer type as an explicit value is often
inconvenient since the modulus is by definition too big to be represented by the
type.
The modular inverse is computed much more efficiently than the general case
using the algorithm described in https://arxiv.org/pdf/2204.04342.pdf.
!!! compat "Julia 1.11"
The single-argument method of `invmod` requires Julia 1.11 or later.
"""
invmod(n::Integer, ::Type{T}) where {T<:BitInteger} = invmod(n % T)

function invmod(n::T) where {T<:BitInteger}
isodd(n) || throw(DomainError(n, "Argument must be odd."))
x = (3*n 2) % T
y = (1 - n*x) % T
for _ = 1:trailing_zeros(2*sizeof(T))
x *= y + true
y *= y
end
return x
end

# ^ for any x supporting *
to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x)
@noinline throw_domerr_powbysq(::Any, p) = throw(DomainError(p, LazyString(
Expand Down
21 changes: 20 additions & 1 deletion test/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ end
@test_throws MethodError gcdx(MyOtherRational(2//3), MyOtherRational(3//4))
end

@testset "invmod" begin
@testset "invmod(n, m)" begin
@test invmod(6, 31) === 26
@test invmod(-1, 3) === 2
@test invmod(1, -3) === -2
Expand Down Expand Up @@ -256,6 +256,25 @@ end
end
end

@testset "invmod(n)" begin
for T in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128)
if sizeof(T) 2
# test full domain for small types
for a = typemin(T)+true:T(2):typemax(T)
b = invmod(a)
@test a*b == 1
end
else
# test random sample for large types
for _ = 1:2^12
a = rand(T) | true
b = invmod(a)
@test a*b == 1
end
end
end
end

@testset "powermod" begin
@test powermod(2, 3, 5) == 3
@test powermod(2, 3, -5) == -2
Expand Down

0 comments on commit 63c15e5

Please sign in to comment.