Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make inplace Rational{BigInt} arithmetic from gmplib available #44566

Merged
merged 15 commits into from
Apr 14, 2022
83 changes: 67 additions & 16 deletions base/gmp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,8 @@ module MPQ
import .Base: unsafe_rational, __throw_rational_argerror_zero
import ..GMP: BigInt, MPZ, Limb, isneg

gmpq(op::Symbol) = (Symbol(:__gmpq_, op), :libgmp)

mutable struct _MPQ
num_alloc::Cint
num_size::Cint
Expand Down Expand Up @@ -907,70 +909,119 @@ end
function Rational{BigInt}(num::BigInt, den::BigInt)
if iszero(den)
iszero(num) && __throw_rational_argerror_zero(BigInt)
num = isneg(num) ? -one(BigInt) : one(BigInt)
return unsafe_rational(BigInt, num, den)
return set_si(flipsign(1, num), 0)
end
xq = _MPQ(MPZ.set(num), MPZ.set(den))
ccall((:__gmpq_canonicalize, :libgmp), Cvoid, (mpq_t,), xq)
return sync_rational!(xq)
end

function Base.:+(x::Rational{BigInt}, y::Rational{BigInt})
# define set, set_ui, set_si, set_z, and their inplace versions
function set!(z::Rational{BigInt}, x::Rational{BigInt})
zq = _MPQ(z)
ccall((:__gmpq_set, :libgmp), Cvoid, (mpq_t, mpq_t), zq, _MPQ(x))
return sync_rational!(zq)
end

function set_z!(z::Rational{BigInt}, x::BigInt)
zq = _MPQ(z)
ccall((:__gmpq_set_z, :libgmp), Cvoid, (mpq_t, MPZ.mpz_t), zq, x)
return sync_rational!(zq)
end

for (op, T) in ((:set, Rational{BigInt}), (:set_z, BigInt))
op! = Symbol(op, :!)
@eval $op(a::$T) = $op!(unsafe_rational(BigInt(), BigInt()), a)
end

# note that rationals returned from set_ui and set_si are not checked,
# set_ui(0, 0) will return 0//0 without errors, just like unsafe_rational
for (op, T1, T2) in ((:set_ui, Culong, Culong), (:set_si, Clong, Culong))
op! = Symbol(op, :!)
@eval begin
function $op!(z::Rational{BigInt}, a, b)
zq = _MPQ(z)
ccall($(gmpq(op)), Cvoid, (mpq_t, $T1, $T2), zq, a, b)
return sync_rational!(zq)
end
$op(a, b) = $op!(unsafe_rational(BigInt(), BigInt()), a, b)
end
end

# define add, sub, mul, div, and their inplace versions
function add!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
if iszero(x.den) || iszero(y.den)
if iszero(x.den) && iszero(y.den) && isneg(x.num) != isneg(y.num)
throw(DivideError())
end
return iszero(x.den) ? x : y
return set!(z, iszero(x.den) ? x : y)
end
zq = _MPQ()
zq = _MPQ(z)
ccall((:__gmpq_add, :libgmp), Cvoid,
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
return sync_rational!(zq)
end
function Base.:-(x::Rational{BigInt}, y::Rational{BigInt})

function sub!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
if iszero(x.den) || iszero(y.den)
if iszero(x.den) && iszero(y.den) && isneg(x.num) == isneg(y.num)
throw(DivideError())
end
return iszero(x.den) ? x : -y
iszero(x.den) && return set!(z, x)
return set_si!(z, flipsign(-1, y.num), 0)
end
zq = _MPQ()
zq = _MPQ(z)
ccall((:__gmpq_sub, :libgmp), Cvoid,
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
return sync_rational!(zq)
end
function Base.:*(x::Rational{BigInt}, y::Rational{BigInt})

function mul!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
if iszero(x.den) || iszero(y.den)
if iszero(x.num) || iszero(y.num)
throw(DivideError())
end
return xor(isneg(x.num),isneg(y.num)) ? -one(BigInt)//zero(BigInt) : one(BigInt)//zero(BigInt)
return set_si!(z, ifelse(xor(isneg(x.num), isneg(y.num)), -1, 1), 0)
end
zq = _MPQ()
zq = _MPQ(z)
ccall((:__gmpq_mul, :libgmp), Cvoid,
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
return sync_rational!(zq)
end
function Base.://(x::Rational{BigInt}, y::Rational{BigInt})

function div!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
if iszero(x.den)
if iszero(y.den)
throw(DivideError())
end
return isneg(y.num) ? -x : x
isneg(y.num) || return set!(z, x)
return set_si!(z, flipsign(-1, x.num), 0)
elseif iszero(y.den)
return y.den // y.num
return set_si!(z, 0, 1)
elseif iszero(y.num)
if iszero(x.num)
throw(DivideError())
end
return (isneg(x.num) ? -one(BigInt) : one(BigInt)) // y.num
return set_si!(z, flipsign(1, x.num), 0)
end
zq = _MPQ()
zq = _MPQ(z)
ccall((:__gmpq_div, :libgmp), Cvoid,
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
return sync_rational!(zq)
end

for (fJ, fC) in ((:+, :add), (:-, :sub), (:*, :mul), (://, :div))
fC! = Symbol(fC, :!)
@eval begin
($fC!)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(x, x, y)
(Base.$fJ)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(unsafe_rational(BigInt(), BigInt()), x, y)
sumiya11 marked this conversation as resolved.
Show resolved Hide resolved
end
end

function Base.cmp(x::Rational{BigInt}, y::Rational{BigInt})
Int(ccall((:__gmpq_cmp, :libgmp), Cint, (mpq_t, mpq_t), _MPQ(x), _MPQ(y)))
end

end # MPQ module

end # module
161 changes: 161 additions & 0 deletions test/gmp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,164 @@ end
@test T(big"2"^(n+1) - big"2"^(n-precision(T)) - 1) === floatmax(T)
end
end

a = Rational{BigInt}(12345678901234567890123456789, 987654321987654320)
b = Rational{BigInt}(12345678902222222212111111109, 987654321987654320)
c = Rational{BigInt}(24691357802469135780246913578, 987654321987654320)
d = Rational{BigInt}(- 12345678901234567890123456789, 493827160993827160)
e = Rational{BigInt}(12345678901234567890123456789, 12345678902222222212111111109)
@testset "big rational basics" begin
@test a+BigInt(1) == b
@test typeof(a+1) == Rational{BigInt}
@test a+1 == b
@test isequal(a+1, b)
@test b == a+1
@test !(b == a)
@test b > a
@test b >= a
@test !(b < a)
@test !(b <= a)

@test typeof(a * 2) == Rational{BigInt}
@test a*2 == c
@test c-a == a
@test c == a + a
@test c+1 == a+b

@test typeof(d) == Rational{BigInt}
@test d == -c


@test e == a // b

@testset "gmp cmp" begin
@test Base.GMP.MPQ.cmp(b, a) == 1
@test Base.GMP.MPQ.cmp(a, b) == -1
@test Base.GMP.MPQ.cmp(a, a) == 0
end

@testset "division errors" begin
oz = Rational{BigInt}(0, 1)
zo = Rational{BigInt}(1, 0)

@test oz + oz == 3 * oz == oz
@test oz // zo == oz
@test zo // oz == zo

@test_throws DivideError() zo - zo
@test_throws DivideError() zo + (-zo)
@test_throws DivideError() zo * oz
@test_throws DivideError() oz // oz
@test_throws DivideError() zo // zo
end

@testset "big infinities" begin
oz = Rational{BigInt}(1, 0)
zo = Rational{BigInt}(0, 1)
o = Rational{BigInt}(1, 1)

@test oz + zo == oz
@test zo - oz == -oz
@test zo + (-oz) == -oz
@test -oz + zo == -oz

@test (-oz) * (-oz) == oz
@test (-oz) * oz == -oz

@test o // zo == oz
@test (-o) // zo == -oz

@test Rational{BigInt}(-1, 0) == -1//0
@test Rational{BigInt}(1, 0) == 1//0
end
end


aa = 1//2
bb = -1//3
cc = 3//2
a = Rational{BigInt}(aa)
b = Rational{BigInt}(bb)
c = Rational{BigInt}(cc)
t = Rational{BigInt}(0, 1)
@testset "big rational inplace" begin
sumiya11 marked this conversation as resolved.
Show resolved Hide resolved
@test Base.GMP.MPQ.add!(t, a, b) == 1//6
@test t == 1//6
@test Base.GMP.MPQ.add!(t, t) == 1//3
@test t == 1//3

@test iszero(Base.GMP.MPQ.sub!(t, t))
@test iszero(t)
@test Base.GMP.MPQ.sub!(t, b, c) == -11//6
@test t == -11//6

@test Base.GMP.MPQ.mul!(t, a, b) == -1//6
@test t == -1//6
@test Base.GMP.MPQ.mul!(t, t) == 1//36
@test t == 1//36
@test iszero(Base.GMP.MPQ.mul!(t, Rational{BigInt}(0)))

@test Base.GMP.MPQ.div!(t, a, b) == -3//2
@test t == -3//2
@test Base.GMP.MPQ.div!(t, a) == -3//1
@test t == -3//1

@test aa == a && bb == b && cc == c

@testset "set" begin
@test Base.GMP.MPQ.set!(a, b) == b
@test a == b == bb

Base.GMP.MPQ.add!(a, b, c)
@test b == bb

@test Base.GMP.MPQ.set_z!(a, BigInt(0)) == 0
@test iszero(a)
@test Base.GMP.MPQ.set_z!(a, BigInt(3)) == 3
@test a == BigInt(3)

@test Base.GMP.MPQ.set_ui(1, 2) == 1//2
@test Base.GMP.MPQ.set_ui(0, 1) == 0//1
@test Base.GMP.MPQ.set_ui!(a, 1, 2) == 1//2
@test a == 1//2

@test Base.GMP.MPQ.set_si(1, 2) == 1//2
@test Base.GMP.MPQ.set_si(-1, 2) == -1//2
@test Base.GMP.MPQ.set_si!(a, -1, 2) == -1//2
@test a == -1//2
end

@testset "infinities" begin
oz = Rational{BigInt}(1, 0)
zo = Rational{BigInt}(0, 1)
oo = Rational{BigInt}(1, 1)

@test Base.GMP.MPQ.add!(zo, oz) == oz
@test zo == oz
zo = Rational{BigInt}(0, 1)

@test Base.GMP.MPQ.sub!(zo, oz) == -oz
@test zo == -oz
zo = Rational{BigInt}(0, 1)

@test Base.GMP.MPQ.add!(zo, -oz) == -oz
@test zo == -oz
zo = Rational{BigInt}(0, 1)

@test Base.GMP.MPQ.sub!(zo, -oz) == oz
@test zo == oz
zo = Rational{BigInt}(0, 1)

@test Base.GMP.MPQ.mul!(-oz, -oz) == oz
@test Base.GMP.MPQ.mul!(-oz, oz) == -oz
@test Base.GMP.MPQ.mul!(oz, -oz) == -1//0
@test oz == -1//0
oz = Rational{BigInt}(1, 0)

@test Base.GMP.MPQ.div!(oo, zo) == oz
@test oo == oz
oo = Rational{BigInt}(1, 1)

@test Base.GMP.MPQ.div!(-oo, zo) == -oz
end
end