Skip to content

Commit 22c0e7e

Browse files
authored
impliment iroot and friends for native julia. Much faster for native types (#25)
1 parent edff333 commit 22c0e7e

4 files changed

Lines changed: 350 additions & 27 deletions

File tree

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ uuid = "18e54dd8-cb9d-406c-a71d-865a43cbb235"
33
version = "0.1.3"
44

55
[extras]
6+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
67
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
78

89
[targets]
9-
test = ["Test"]
10+
test = ["Test", "Random"]
1011

1112
[compat]
1213
julia = "1.6"

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# IntegerMathUtils.jl
22

3-
This library adds several functions useful for doing math on integers. Most of these are GMP wrappers that may have faster implimentations for smaller integer types.
3+
This library adds several functions useful for doing math on integers. Machine integers (and any other non-`BigInt` `Integer` type) use native Julia algorithms; `BigInt` inputs use GMP.
44

55
**Functions**
66

7-
* `iroot(x::Integer, n::integer)` the integer nth root of `x`. Specifically, this is the largest integer `a` such that `a^n <= x`. Note that `n` must fit into an `Int64` (for GMP compatability).
8-
* `ispower(x::Integer)` return if there are integer `base` and `exponent>1` values such that `base^exponent = x`.
9-
* `find_exponent(x::Integer)` returns the largest possible integer `exponent` such that `base^exponent = x` for some `base`. Returns `1` for `x ∈ [0,1]`.
7+
* `iroot(x::Integer, n::Integer)` the integer nth root of `x`, truncated toward zero: for `x >= 0` the largest integer `a` such that `a^n <= x`, and `iroot(-x, n) == -iroot(x, n)` for odd `n` (e.g. `iroot(-9, 3) == -2`). Throws a `DomainError` for `n <= 0` or an even root of a negative number.
8+
* `rootrem(x::Integer, n::Integer)` returns `(iroot(x, n), x - iroot(x, n)^n)`.
9+
* `ispower(x::Integer)` returns whether there are integer `base` and `exponent > 1` values such that `base^exponent == x`. For negative `x` only odd exponents count, so `ispower(-8) == true` but `ispower(-4) == false`. `0` and `±1` are perfect powers.
10+
* `find_exponent(x::Integer)` returns the largest possible integer `exponent` such that `base^exponent == x` for some `base` (only odd exponents for negative `x`). Returns `1` for `x ∈ [-1, 1]`.
1011
* `is_probably_prime(x::Integer; reps=25)` returns if `x` is prime. Will be incorrect less than `4^-reps` of the time.
11-
* `kronecker(a::Integer, n::Integer)` Computes the [Kronecker_symbol](https://en.wikipedia.org/wiki/Kronecker_symbol) which is a generalization of the legendre and jacobi symbols.
12+
* `kronecker(a::Integer, n::Integer)` Computes the [Kronecker symbol](https://en.wikipedia.org/wiki/Kronecker_symbol) which is a generalization of the legendre and jacobi symbols.

src/IntegerMathUtils.jl

Lines changed: 157 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,181 @@
11
module IntegerMathUtils
22
export iroot, ispower, rootrem, find_exponent, is_probably_prime, kronecker
33

4-
iroot(x::Integer, n::Integer) = iroot(x, Cint(n))
4+
using Base: uabs, BitUnsigned64
55

6-
# TODO: Add more efficient implimentation
7-
iroot(x::T, n::Cint) where {T<:Integer} = T(iroot(big(x), Cint(n)))
6+
@static if isdefined(Base, :top_set_bit)
7+
using Base: top_set_bit
8+
else
9+
top_set_bit(x::Base.BitInteger) = 8sizeof(x) - leading_zeros(x)
10+
top_set_bit(x::Integer) = ndigits(x, base=2)
11+
end
812

9-
function iroot(x::BigInt, n::Cint)
13+
function iroot(x::BigInt, n::Integer)
1014
n <= 0 && throw(DomainError(n, "Exponent must be > 0"))
11-
x <= 0 && iseven(n) && throw(DomainError(n, "This is a math no-no"))
15+
x < 0 && iseven(n) && throw(DomainError(x, "Even roots require x >= 0"))
1216
ans = BigInt()
13-
@ccall :libgmp.__gmpz_root(ans::Ref{BigInt}, x::Ref{BigInt}, n::Cint)::Cint
17+
@ccall :libgmp.__gmpz_root(ans::Ref{BigInt}, x::Ref{BigInt}, n::Culong)::Cint
1418
ans
1519
end
1620

17-
# TODO: Add more efficient implimentation for smaller types
18-
function rootrem(x::T, n::Integer) where {T<:Integer}
19-
x = big(x)
20-
n = Cint(n)
21+
function rootrem(x::BigInt, n::Integer)
2122
n <= 0 && throw(DomainError(n, "Exponent must be > 0"))
22-
x <= 0 && iseven(x) && throw(DomainError(n, "This is a math no-no"))
23+
x < 0 && iseven(n) && throw(DomainError(x, "Even roots require x >= 0"))
2324
root = BigInt()
2425
rem = BigInt()
25-
@ccall :libgmp.__gmpz_rootrem(root::Ref{BigInt}, rem::Ref{BigInt}, x::Ref{BigInt}, n::Int)::Nothing
26-
return (root, T(rem))
26+
@ccall :libgmp.__gmpz_rootrem(root::Ref{BigInt}, rem::Ref{BigInt}, x::Ref{BigInt}, n::Culong)::Cvoid
27+
return (root, rem)
2728
end
2829

29-
# TODO: Add more efficient implimentation for smaller types
30-
ispower(x::Integer) = ispower(big(x))
31-
3230
function ispower(x::BigInt)
3331
return 0 != @ccall :libgmp.__gmpz_perfect_power_p(x::Ref{BigInt})::Cint
3432
end
3533

36-
# TODO: Add more efficient implimentation for smaller types
34+
# a^n in T via power-by-squaring, detecting overflow. Returns (value, overflowed)
35+
function pow_with_overflow(a::T, n::Integer) where T<:Base.BitInteger
36+
p = one(T)
37+
while n > 0
38+
if isodd(n)
39+
p, o = Base.Checked.mul_with_overflow(p, a)
40+
o && return (p, true)
41+
end
42+
n >>= 1
43+
n > 0 || break
44+
a, o = Base.Checked.mul_with_overflow(a, a)
45+
o && return (p, true)
46+
end
47+
return (p, false)
48+
end
49+
function _pow_leq(a::T, n::Integer, x::T) where T<:Base.BitInteger
50+
p, o = pow_with_overflow(a, n)
51+
return !o && p <= x
52+
end
53+
_pow_leq(a::T, n::Integer, x::T) where T<:Integer = a^n <= x
54+
55+
# Float64-accurate estimate of x^(1/n) for x <= typemax(UInt128).
56+
# Because the root must be small, this is within +-1 of the true root when n>2.
57+
function _iroot_approx(x::T, n::Integer) where T<:Base.BitInteger
58+
trunc(T, exp(log(Float64(x)) / n))
59+
end
60+
61+
# A Float64-accurate estimate of x^(1/n) for x > typemax(UInt128)
62+
# Split x = hi*2^sh with hi its top 53 bits, estimate log2(root) = (log2(hi)+sh)/n
63+
# and place a 53-bit mantissa at the right exponent.
64+
function _iroot_float(x::T, n::Integer) where T<:Integer
65+
b = top_set_bit(x)
66+
sh = b - 53
67+
L = (log2(Float64(x >> sh)) + sh) / n
68+
e = floor(Int, L)
69+
m53 = trunc(T, exp2(L - e) * exp2(53))
70+
return e >= 53 ? (m53 << (e - 53)) : (m53 >> (53 - e))
71+
end
72+
73+
# One integer Newton step
74+
@inline function _iroot_newton(x::T, n::Integer, a::T) where T<:Integer
75+
return ((n - 1) * a + x ÷ a^(n - 1)) ÷ n
76+
end
77+
78+
function _iroot_approx(x::T, n::Integer) where T<:Integer
79+
x <= typemax(UInt128) && return T(_iroot_approx(UInt128(x), n))
80+
# one unconditional step lifts any seed to >= floor(root)-1 (by weighted AM-GM)
81+
a = _iroot_newton(x, n, _iroot_float(x, n))
82+
while true # after which the iteration descends until return
83+
b = _iroot_newton(x, n, a)
84+
b >= a && return a
85+
a = b
86+
end
87+
end
88+
89+
# floor(x^(1/n)) for x >= 0; n >= 2 (n == 2 only for non-bit Integer types,
90+
# machine ints take the Base.isqrt path in iroot)
91+
function _iroot_nonneg(x::T, n::Integer) where T<:Integer
92+
x <= one(T) && return x
93+
n >= top_set_bit(x) && return one(T) # 2^n > x
94+
a = _iroot_approx(x, n)
95+
if _pow_leq(a, n, x)
96+
while _pow_leq(a + one(T), n, x)
97+
a += one(T)
98+
end
99+
else
100+
a -= one(T)
101+
while !_pow_leq(a, n, x)
102+
a -= one(T)
103+
end
104+
end
105+
return a
106+
end
107+
108+
# Base.isqrt is already optimal for machine ints; its generic Integer
109+
# fallback is Float64-based though, which is wrong for wide values.
110+
_isqrt(x::Base.BitInteger) = Base.isqrt(x)
111+
_isqrt(x::Integer) = _iroot_nonneg(x, 2)
112+
113+
function iroot(x::T, n::Integer) where T<:Integer
114+
n <= 0 && throw(DomainError(n, "Exponent must be > 0"))
115+
x < 0 && iseven(n) && throw(DomainError(x, "Even roots require x >= 0"))
116+
n == 1 && return x
117+
n == 2 && return T(_isqrt(uabs(x))) # x >= 0 here
118+
r = T(_iroot_nonneg(uabs(x), n))
119+
return x < 0 ? -r : r
120+
end
121+
122+
function rootrem(x::Integer, n::Integer)
123+
a = iroot(x, n)
124+
return (a, x - a^n)
125+
end
126+
127+
# Is m (>= 2) an exact k-th power (k odd, >= 3)?
128+
# Callers handle k == 2 via _isqrt directly
129+
# (its root can be large enough that the float error below exceeds 0.5).
130+
# The float root r = exp(lm/k) has provable worst-case error
131+
# E <= 1.5*r*(|ln r|+1)*2^-52; for k >= 3 and m < 2^128 this is << 0.5
132+
# so round(r) is the only integer candidate
133+
function _kth_root_exact(m::T, k::Integer, logm::Float64) where T<:Base.BitInteger
134+
lr = logm / k
135+
r = exp(lr)
136+
tol = 2 * r * (abs(lr) + 1) * exp2(-52)
137+
b = round(r)
138+
abs(r - b) > tol && return false
139+
c = unsafe_trunc(T, b)
140+
p, o = pow_with_overflow(c, k)
141+
return !o && p == m
142+
end
143+
# Arbitrary-precision types: no reliable Float64, so verify via the native root.
144+
function _kth_root_exact(m::T, k::Integer, logm::Float64) where T<:Integer
145+
a = iroot(m, k)
146+
return a^k == m # a^k <= m, so no overflow
147+
end
148+
149+
function ispower(x::Integer)
150+
ux = uabs(x)
151+
ux <= one(ux) && return true
152+
neg = x < 0
153+
trailing_zeros(ux) == 1 && return false # 2 | x exactly once: not a power
154+
if !neg
155+
a = _isqrt(ux)
156+
a * a == ux && return true
157+
end
158+
lx = log(Float64(ux))
159+
for k in 3:2:top_set_bit(ux)
160+
_kth_root_exact(ux, k, lx)[1] && return true
161+
end
162+
return false
163+
end
164+
37165
function find_exponent(x::Integer)
38-
x <= 1 && return 1
39-
for exponent in ndigits(x, base=2):-1:2
40-
rootrem(x, exponent)[2] == 0 && return exponent
166+
ux = uabs(x)
167+
ux <= one(ux) && return 1
168+
neg = x < 0
169+
lx = log(Float64(ux))
170+
for e in top_set_bit(ux):-1:3
171+
neg && iseven(e) && continue
172+
_kth_root_exact(ux, e, lx)[1] && return e
173+
end
174+
if !neg
175+
a = _isqrt(ux)
176+
a * a == ux && return 2
41177
end
42-
1
178+
return 1
43179
end
44180

45181
function is_probably_prime(x::Integer; reps=25)

0 commit comments

Comments
 (0)