Skip to content

Commit

Permalink
Initial hashing support
Browse files Browse the repository at this point in the history
  • Loading branch information
barucden committed Oct 20, 2024
1 parent 88e65fe commit db600fe
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/Decimals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ struct Decimal <: AbstractFloat
Decimal(s::Integer, c::Integer, e::Integer) = new(Bool(s), c, e)
end

include("bigint.jl")

# Convert between Decimal objects, numbers, and strings
include("decimal.jl")

Expand All @@ -35,4 +37,6 @@ include("equals.jl")
# Rounding
include("round.jl")

include("hash.jl")

end
55 changes: 55 additions & 0 deletions src/bigint.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@static if VERSION < v"1.10-"
const libgmp = :libgmp
else
const libgmp = Base.GMP.libgmp
end

function isdivisible(x::BigInt, n::Int)
r = ccall((:__gmpz_divisible_ui_p, libgmp), Cint,
(Base.GMP.MPZ.mpz_t, Culong), x, n)
return r != 0
end

function exactdiv(x::BigInt, n::Int)
y = BigInt()
ccall((:__gmpz_divexact_ui, libgmp), Cvoid,
(Base.GMP.MPZ.mpz_t, Base.GMP.MPZ.mpz_t, Culong), y, x, n)
return y
end

"""
maxexp(n)
Return maximum exponent E such that n^E is representable both as Int and
Culong (i.e., n^(E+1) would overflow).
"""
function maxexp(n::Int)
maxval = min(typemax(Culong), typemax(Int))
return ceil(Int, log(n, maxval)) - 1
end

"""
cancelfactor(x::BigInt, ::Val{N})
Remove all occurrences of the factor `N` from `x`. The result is pair `(y, E)`
such that `x = y * N^E`.
"""
function cancelfactor(x::BigInt, ::Val{N}) where {N}
if iszero(x)
return x, 0
end

q = 0
while isdivisible(x, N)
d = N
q += 1
for e in 2:maxexp(N)
isdivisible(x, d * N) || break
d *= N
q += 1
end
x = exactdiv(x, d)
end

return x, q
end
1 change: 0 additions & 1 deletion src/decimal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,3 @@ end

# sign
Base.signbit(x::Decimal) = x.s

2 changes: 0 additions & 2 deletions src/equals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ function Base.:(==)(x::Decimal, y::Decimal)
a.c == b.c && a.q == b.q && a.s == b.s
end

Base.iszero(x::Decimal) = iszero(x.c)

function Base.:(<)(x::Decimal, y::Decimal)
# return early on zero
if iszero(x) && iszero(y)
Expand Down
16 changes: 16 additions & 0 deletions src/hash.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function Base.decompose(x::Decimal)
if iszero(x)
return (big(0), 0, big((-1)^x.s))
end

coef = (-1)^x.s * x.c

if x.q 0
return (coef * big(5)^x.q, x.q, big(1))
else
coef, exp = cancelfactor(coef, Val(5))
q = -x.q - exp
return (coef, x.q, big(5) ^ q)
end
end

6 changes: 4 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ global d = [
Decimal(true, 4, -6)
]

include("test_arithmetic.jl")
include("test_bigint.jl")
include("test_constructor.jl")
include("test_decimal.jl")
include("test_norm.jl")
include("test_arithmetic.jl")
include("test_equals.jl")
include("test_hash.jl")
include("test_norm.jl")
include("test_round.jl")

end
44 changes: 44 additions & 0 deletions test/test_bigint.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@testset "BigInt" begin
@testset "isdivisible" begin
@test Decimals.isdivisible(big(12), 1)
@test Decimals.isdivisible(big(12), 2)
@test Decimals.isdivisible(big(12), 3)
@test Decimals.isdivisible(big(12), 4)
@test Decimals.isdivisible(big(12), 6)

prime = big(1709)
@test Decimals.isdivisible(prime, 1)
@test Decimals.isdivisible(prime, Int(prime))
@test !any(n -> Decimals.isdivisible(prime, n), 2:1708)
end

@testset "exactdiv" begin
@test Decimals.exactdiv(big(12), 2) == big(6)
@test Decimals.exactdiv(big(12), 3) == big(4)
@test Decimals.exactdiv(big(12), 4) == big(3)
@test Decimals.exactdiv(big(12), 6) == big(2)
end

@testset "maxexp" begin
T = ifelse(typemax(Culong) > typemax(Int), Int, Culong)

E = Decimals.maxexp(2)
@test T(2)^E == big(2)^E
@test T(2)^(E + 1) != big(2)^(E+1)

E = Decimals.maxexp(5)
@test T(5)^E == big(5)^E
@test T(5)^(E + 1) != big(5)^(E+1)

E = Decimals.maxexp(10)
@test T(10)^E == big(10)^E
@test T(10)^(E + 1) != big(10)^(E+1)
end

@testset "cancelfactor" begin
@test Decimals.cancelfactor(big(0), Val(3)) == (big(0), 0)
@test Decimals.cancelfactor(big(3)^8, Val(3)) == (big(1), 8)
@test Decimals.cancelfactor(big(3)^128, Val(3)) == (big(1), 128)
@test Decimals.cancelfactor(big(948659)^8, Val(948659)) == (big(1), 8)
end
end
21 changes: 21 additions & 0 deletions test/test_hash.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Decimals
using Test

@testset "Hashing" begin
@testset "hash" begin
@test hash(Decimal(0.)) == hash(0.)
@test hash(Decimal(-0.)) == hash(-0.)
@test hash(Decimal(3)) == hash(3)
@test hash(Decimal(0.09375)) == hash(0.09375)
@test hash(Decimal(-3)) == hash(-3)
@test hash(Decimal(-0.09375)) == hash(-0.09375)

# Equality implies same hash
@test hash(Decimal(0, 100, 0)) == hash(Decimal(0, 10, 1))
@test hash(Decimal(0, 100, 0)) == hash(Decimal(0, 1, 2))
@test hash(Decimal(1, 100, 0)) == hash(Decimal(1, 10, 1))
@test hash(Decimal(1, 100, 0)) == hash(Decimal(1, 1, 2))

@test hash(Decimal(0, 310, -2)) == hash(Decimal(0, 31, -1))
end
end

0 comments on commit db600fe

Please sign in to comment.