Skip to content

Commit

Permalink
Make low-level ZZRingElem accessors more powerful (#1942)
Browse files Browse the repository at this point in the history
... and their code more readable (at least to me).

Also implement `rem(::ZZRingElem, ::Type{UInt})` and use that to
speed up `iseven`, `isodd`, `sign`, `signbit` and `is_squarefree`
for `ZZRingElem`.

When tested in isolation using BenchmarkTools, the new methods
are usually a little bit faster, and never slower,
than the old ones. The main gain however is in code using them:
unlike opaque `ccall`s the compiler can inline these helper and
possibly perform further optimizations.
  • Loading branch information
fingolfin authored Nov 12, 2024
1 parent d29b031 commit f4ac159
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 37 deletions.
68 changes: 31 additions & 37 deletions src/flint/fmpz.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,23 @@ end
################################################################################

# Similar to hash for BigInt found in julia/base
@inline function _fmpz_is_small(a::ZZRingElemOrPtr)
return __fmpz_is_small(data(a))
end

@inline function _fmpz_is_small(a::ZZRingElem)
return __fmpz_is_small(a.d)
# "views" a non-small ZZRingElem as a readonly BigInt
# the caller must ensure z is actually non-small, and
# call GC.@preserve appropriately
function _as_bigint(a::ZZRingElemOrPtr)
unsafe_load(Ptr{BigInt}(data(a) << 2))
end

@inline function _fmpz_limbs(a::ZZRingElem)
return __fmpz_limbs(a.d)
@inline function _fmpz_size(a::ZZRingElemOrPtr)
return _fmpz_is_small(a) ? 1 : GC.@preserve a _as_bigint(a).size
end

function hash_integer(a::ZZRingElem, h::UInt)
return _hash_integer(a.d, h)
return GC.@preserve a _hash_integer(data(a), h)
end

function hash(a::ZZRingElem, h::UInt)
Expand All @@ -164,20 +170,12 @@ end
return (unsigned(a) >> (Sys.WORD_SIZE - 2) != 1)
end

function __fmpz_limbs(a::Int)
if __fmpz_is_small(a)
return Cint(0)
end
b = unsafe_load(convert(Ptr{Cint}, unsigned(a)<<2), 2)
return b
end

function _hash_integer(a::Int, h::UInt)
s::Cint = __fmpz_limbs(a)
s == 0 && return Base.hash_integer(a, h)
# get the pointer after the first two Cint
d = convert(Ptr{Ptr{UInt}}, unsigned(a) << 2) + 2*sizeof(Cint)
p = unsafe_load(d)
__fmpz_is_small(a) && return Base.hash_integer(a, h)
# view it as a BigInt
z = unsafe_load(Ptr{BigInt}(unsigned(a) << 2))
s = z.size # number of active limbs
p = z.d # pointer to the limbs
b = unsafe_load(p)
h = xor(Base.hash_uint(xor(ifelse(s < 0, -b, b), h)), h)
for k = 2:abs(s)
Expand Down Expand Up @@ -215,11 +213,11 @@ zero(::Type{ZZRingElem}) = ZZRingElem(0)
Return the sign of $a$, i.e. $+1$, $0$ or $-1$.
"""
sign(a::ZZRingElemOrPtr) = ZZRingElem(@ccall libflint.fmpz_sgn(a::Ref{ZZRingElem})::Cint)
sign(a::ZZRingElemOrPtr) = ZZRingElem(sign(Int, a))

sign(::Type{Int}, a::ZZRingElemOrPtr) = Int(@ccall libflint.fmpz_sgn(a::Ref{ZZRingElem})::Cint)
sign(::Type{Int}, a::ZZRingElemOrPtr) = is_zero(a) ? 0 : (signbit(a) ? -1 : 1)

Base.signbit(a::ZZRingElemOrPtr) = signbit(sign(Int, a))
Base.signbit(a::ZZRingElemOrPtr) = _fmpz_is_small(a) ? signbit(data(a)) : signbit(_fmpz_size(a))

is_negative(n::ZZRingElemOrPtr) = sign(Int, n) < 0
is_positive(n::ZZRingElemOrPtr) = sign(Int, n) > 0
Expand Down Expand Up @@ -252,7 +250,7 @@ end
Return the number of limbs required to store the absolute value of $a$.
"""
size(a::ZZRingElem) = Int(@ccall libflint.fmpz_size(a::Ref{ZZRingElem})::Cint)
size(a::ZZRingElem) = _fmpz_is_small(a) ? 1 : abs(_fmpz_size(a))

is_unit(a::ZZRingElemOrPtr) = data(a) == 1 || data(a) == -1

Expand Down Expand Up @@ -284,8 +282,8 @@ function numerator(a::ZZRingElem)
return a
end

isodd(a::ZZRingElemOrPtr) = (@ccall libflint.fmpz_is_odd(a::Ref{ZZRingElem})::Cint) % Bool
iseven(a::ZZRingElemOrPtr) = (@ccall libflint.fmpz_is_even(a::Ref{ZZRingElem})::Cint) % Bool
isodd(a::ZZRingElemOrPtr) = isodd(a % UInt)
iseven(a::ZZRingElemOrPtr) = !isodd(a)

###############################################################################
#
Expand Down Expand Up @@ -475,6 +473,10 @@ function is_divisible_by(x::Integer, y::ZZRingElem)
return is_divisible_by(ZZRingElem(x), y)
end

function rem(x::ZZRingElemOrPtr, ::Type{UInt64})
return _fmpz_is_small(x) ? (data(x) % UInt) : GC.@preserve x (_as_bigint(x) % UInt)
end

function rem(x::ZZRingElem, c::ZZRingElem)
q, r = Base.divrem(x, c)
return r
Expand Down Expand Up @@ -1527,7 +1529,7 @@ end
Return `true` if $n$ is squarefree, `false` otherwise.
"""
function is_squarefree(n::Union{Int, ZZRingElem})
iszero(n) && return false
is_divisible_by_four(n) && return false
is_unit(n) && return true
e, b = is_perfect_power_with_data(n)
if e > 1
Expand All @@ -1536,6 +1538,9 @@ function is_squarefree(n::Union{Int, ZZRingElem})
return isone(maximum(values(factor(n).fac); init = 1))
end

is_divisible_by_four(n::Integer) = (n % 4) == 0
is_divisible_by_four(n::ZZRingElem) = ((n % UInt) % 4) == 0

################################################################################
#
# Factor trial range
Expand Down Expand Up @@ -2713,13 +2718,6 @@ end

using Base.GMP: Limb, MPZ

# "views" a non-small ZZRingElem as a readonly BigInt
# the caller must call GC.@preserve appropriately
function _as_bigint(z::ZZRingElem)
@assert !_fmpz_is_small(z)
unsafe_load(Ptr{BigInt}(z.d << 2))
end


Random.Sampler(::Type{<:AbstractRNG}, r::StepRange{ZZRingElem, ZZRingElem}, ::Random.Repetition) =
SamplerFmpz(r)
Expand Down Expand Up @@ -2752,11 +2750,7 @@ function SamplerFmpz(r::StepRange{ZZRingElem, ZZRingElem})
nlimbs, highbits = divrem(nd, 8*sizeof(Limb))
highbits > 0 && (nlimbs += 1)
mask = highbits == 0 ? ~zero(Limb) : one(Limb)<<highbits - one(Limb)
GC.@preserve r1 r2 begin
a1 = _fmpz_is_small(r1) ? 1 : _as_bigint(r1).size
a2 = _fmpz_is_small(r2) ? 1 : _as_bigint(r2).size
end
nlimbsmax = max(nlimbs, abs(a1), abs(a2))
nlimbsmax = max(nlimbs, size(r1), size(r2))
return SamplerFmpz(r1, m, nlimbs, nlimbsmax, mask, s)
end

Expand Down
6 changes: 6 additions & 0 deletions test/flint/fmpz-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,12 @@ end
@test rem(-12, ZZRingElem(3)) == 0

@test_throws ArgumentError divexact(ZZ(2), 3)

b = ZZ(123)
@test b % UInt == 123 % UInt
@test -b % UInt == -123 % UInt
@test b^29 % UInt == big(123)^29 % UInt
@test -b^29 % UInt == -big(123)^29 % UInt
end

@testset "ZZRingElem.shift.." begin
Expand Down

0 comments on commit f4ac159

Please sign in to comment.