Skip to content
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)
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
@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