Skip to content

Commit 02b6d99

Browse files
sumiya11Liozou
andauthored
make inplace Rational{BigInt} arithmetic from gmplib available (#44566)
Co-authored-by: Lionel Zoubritzky <[email protected]>
1 parent fbec395 commit 02b6d99

File tree

2 files changed

+228
-16
lines changed

2 files changed

+228
-16
lines changed

base/gmp.jl

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,8 @@ module MPQ
875875
import .Base: unsafe_rational, __throw_rational_argerror_zero
876876
import ..GMP: BigInt, MPZ, Limb, isneg
877877

878+
gmpq(op::Symbol) = (Symbol(:__gmpq_, op), :libgmp)
879+
878880
mutable struct _MPQ
879881
num_alloc::Cint
880882
num_size::Cint
@@ -907,70 +909,119 @@ end
907909
function Rational{BigInt}(num::BigInt, den::BigInt)
908910
if iszero(den)
909911
iszero(num) && __throw_rational_argerror_zero(BigInt)
910-
num = isneg(num) ? -one(BigInt) : one(BigInt)
911-
return unsafe_rational(BigInt, num, den)
912+
return set_si(flipsign(1, num), 0)
912913
end
913914
xq = _MPQ(MPZ.set(num), MPZ.set(den))
914915
ccall((:__gmpq_canonicalize, :libgmp), Cvoid, (mpq_t,), xq)
915916
return sync_rational!(xq)
916917
end
917918

918-
function Base.:+(x::Rational{BigInt}, y::Rational{BigInt})
919+
# define set, set_ui, set_si, set_z, and their inplace versions
920+
function set!(z::Rational{BigInt}, x::Rational{BigInt})
921+
zq = _MPQ(z)
922+
ccall((:__gmpq_set, :libgmp), Cvoid, (mpq_t, mpq_t), zq, _MPQ(x))
923+
return sync_rational!(zq)
924+
end
925+
926+
function set_z!(z::Rational{BigInt}, x::BigInt)
927+
zq = _MPQ(z)
928+
ccall((:__gmpq_set_z, :libgmp), Cvoid, (mpq_t, MPZ.mpz_t), zq, x)
929+
return sync_rational!(zq)
930+
end
931+
932+
for (op, T) in ((:set, Rational{BigInt}), (:set_z, BigInt))
933+
op! = Symbol(op, :!)
934+
@eval $op(a::$T) = $op!(unsafe_rational(BigInt(), BigInt()), a)
935+
end
936+
937+
# note that rationals returned from set_ui and set_si are not checked,
938+
# set_ui(0, 0) will return 0//0 without errors, just like unsafe_rational
939+
for (op, T1, T2) in ((:set_ui, Culong, Culong), (:set_si, Clong, Culong))
940+
op! = Symbol(op, :!)
941+
@eval begin
942+
function $op!(z::Rational{BigInt}, a, b)
943+
zq = _MPQ(z)
944+
ccall($(gmpq(op)), Cvoid, (mpq_t, $T1, $T2), zq, a, b)
945+
return sync_rational!(zq)
946+
end
947+
$op(a, b) = $op!(unsafe_rational(BigInt(), BigInt()), a, b)
948+
end
949+
end
950+
951+
# define add, sub, mul, div, and their inplace versions
952+
function add!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
919953
if iszero(x.den) || iszero(y.den)
920954
if iszero(x.den) && iszero(y.den) && isneg(x.num) != isneg(y.num)
921955
throw(DivideError())
922956
end
923-
return iszero(x.den) ? x : y
957+
return set!(z, iszero(x.den) ? x : y)
924958
end
925-
zq = _MPQ()
959+
zq = _MPQ(z)
926960
ccall((:__gmpq_add, :libgmp), Cvoid,
927961
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
928962
return sync_rational!(zq)
929963
end
930-
function Base.:-(x::Rational{BigInt}, y::Rational{BigInt})
964+
965+
function sub!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
931966
if iszero(x.den) || iszero(y.den)
932967
if iszero(x.den) && iszero(y.den) && isneg(x.num) == isneg(y.num)
933968
throw(DivideError())
934969
end
935-
return iszero(x.den) ? x : -y
970+
iszero(x.den) && return set!(z, x)
971+
return set_si!(z, flipsign(-1, y.num), 0)
936972
end
937-
zq = _MPQ()
973+
zq = _MPQ(z)
938974
ccall((:__gmpq_sub, :libgmp), Cvoid,
939975
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
940976
return sync_rational!(zq)
941977
end
942-
function Base.:*(x::Rational{BigInt}, y::Rational{BigInt})
978+
979+
function mul!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
943980
if iszero(x.den) || iszero(y.den)
944981
if iszero(x.num) || iszero(y.num)
945982
throw(DivideError())
946983
end
947-
return xor(isneg(x.num),isneg(y.num)) ? -one(BigInt)//zero(BigInt) : one(BigInt)//zero(BigInt)
984+
return set_si!(z, ifelse(xor(isneg(x.num), isneg(y.num)), -1, 1), 0)
948985
end
949-
zq = _MPQ()
986+
zq = _MPQ(z)
950987
ccall((:__gmpq_mul, :libgmp), Cvoid,
951988
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
952989
return sync_rational!(zq)
953990
end
954-
function Base.://(x::Rational{BigInt}, y::Rational{BigInt})
991+
992+
function div!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt})
955993
if iszero(x.den)
956994
if iszero(y.den)
957995
throw(DivideError())
958996
end
959-
return isneg(y.num) ? -x : x
997+
isneg(y.num) || return set!(z, x)
998+
return set_si!(z, flipsign(-1, x.num), 0)
960999
elseif iszero(y.den)
961-
return y.den // y.num
1000+
return set_si!(z, 0, 1)
9621001
elseif iszero(y.num)
9631002
if iszero(x.num)
9641003
throw(DivideError())
9651004
end
966-
return (isneg(x.num) ? -one(BigInt) : one(BigInt)) // y.num
1005+
return set_si!(z, flipsign(1, x.num), 0)
9671006
end
968-
zq = _MPQ()
1007+
zq = _MPQ(z)
9691008
ccall((:__gmpq_div, :libgmp), Cvoid,
9701009
(mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y))
9711010
return sync_rational!(zq)
9721011
end
9731012

1013+
for (fJ, fC) in ((:+, :add), (:-, :sub), (:*, :mul), (://, :div))
1014+
fC! = Symbol(fC, :!)
1015+
@eval begin
1016+
($fC!)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(x, x, y)
1017+
(Base.$fJ)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(unsafe_rational(BigInt(), BigInt()), x, y)
1018+
end
1019+
end
1020+
1021+
function Base.cmp(x::Rational{BigInt}, y::Rational{BigInt})
1022+
Int(ccall((:__gmpq_cmp, :libgmp), Cint, (mpq_t, mpq_t), _MPQ(x), _MPQ(y)))
1023+
end
1024+
9741025
end # MPQ module
9751026

9761027
end # module

test/gmp.jl

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,164 @@ end
542542
@test T(big"2"^(n+1) - big"2"^(n-precision(T)) - 1) === floatmax(T)
543543
end
544544
end
545+
546+
a = Rational{BigInt}(12345678901234567890123456789, 987654321987654320)
547+
b = Rational{BigInt}(12345678902222222212111111109, 987654321987654320)
548+
c = Rational{BigInt}(24691357802469135780246913578, 987654321987654320)
549+
d = Rational{BigInt}(- 12345678901234567890123456789, 493827160993827160)
550+
e = Rational{BigInt}(12345678901234567890123456789, 12345678902222222212111111109)
551+
@testset "big rational basics" begin
552+
@test a+BigInt(1) == b
553+
@test typeof(a+1) == Rational{BigInt}
554+
@test a+1 == b
555+
@test isequal(a+1, b)
556+
@test b == a+1
557+
@test !(b == a)
558+
@test b > a
559+
@test b >= a
560+
@test !(b < a)
561+
@test !(b <= a)
562+
563+
@test typeof(a * 2) == Rational{BigInt}
564+
@test a*2 == c
565+
@test c-a == a
566+
@test c == a + a
567+
@test c+1 == a+b
568+
569+
@test typeof(d) == Rational{BigInt}
570+
@test d == -c
571+
572+
573+
@test e == a // b
574+
575+
@testset "gmp cmp" begin
576+
@test Base.GMP.MPQ.cmp(b, a) == 1
577+
@test Base.GMP.MPQ.cmp(a, b) == -1
578+
@test Base.GMP.MPQ.cmp(a, a) == 0
579+
end
580+
581+
@testset "division errors" begin
582+
oz = Rational{BigInt}(0, 1)
583+
zo = Rational{BigInt}(1, 0)
584+
585+
@test oz + oz == 3 * oz == oz
586+
@test oz // zo == oz
587+
@test zo // oz == zo
588+
589+
@test_throws DivideError() zo - zo
590+
@test_throws DivideError() zo + (-zo)
591+
@test_throws DivideError() zo * oz
592+
@test_throws DivideError() oz // oz
593+
@test_throws DivideError() zo // zo
594+
end
595+
596+
@testset "big infinities" begin
597+
oz = Rational{BigInt}(1, 0)
598+
zo = Rational{BigInt}(0, 1)
599+
o = Rational{BigInt}(1, 1)
600+
601+
@test oz + zo == oz
602+
@test zo - oz == -oz
603+
@test zo + (-oz) == -oz
604+
@test -oz + zo == -oz
605+
606+
@test (-oz) * (-oz) == oz
607+
@test (-oz) * oz == -oz
608+
609+
@test o // zo == oz
610+
@test (-o) // zo == -oz
611+
612+
@test Rational{BigInt}(-1, 0) == -1//0
613+
@test Rational{BigInt}(1, 0) == 1//0
614+
end
615+
end
616+
617+
618+
aa = 1//2
619+
bb = -1//3
620+
cc = 3//2
621+
a = Rational{BigInt}(aa)
622+
b = Rational{BigInt}(bb)
623+
c = Rational{BigInt}(cc)
624+
t = Rational{BigInt}(0, 1)
625+
@testset "big rational inplace" begin
626+
@test Base.GMP.MPQ.add!(t, a, b) == 1//6
627+
@test t == 1//6
628+
@test Base.GMP.MPQ.add!(t, t) == 1//3
629+
@test t == 1//3
630+
631+
@test iszero(Base.GMP.MPQ.sub!(t, t))
632+
@test iszero(t)
633+
@test Base.GMP.MPQ.sub!(t, b, c) == -11//6
634+
@test t == -11//6
635+
636+
@test Base.GMP.MPQ.mul!(t, a, b) == -1//6
637+
@test t == -1//6
638+
@test Base.GMP.MPQ.mul!(t, t) == 1//36
639+
@test t == 1//36
640+
@test iszero(Base.GMP.MPQ.mul!(t, Rational{BigInt}(0)))
641+
642+
@test Base.GMP.MPQ.div!(t, a, b) == -3//2
643+
@test t == -3//2
644+
@test Base.GMP.MPQ.div!(t, a) == -3//1
645+
@test t == -3//1
646+
647+
@test aa == a && bb == b && cc == c
648+
649+
@testset "set" begin
650+
@test Base.GMP.MPQ.set!(a, b) == b
651+
@test a == b == bb
652+
653+
Base.GMP.MPQ.add!(a, b, c)
654+
@test b == bb
655+
656+
@test Base.GMP.MPQ.set_z!(a, BigInt(0)) == 0
657+
@test iszero(a)
658+
@test Base.GMP.MPQ.set_z!(a, BigInt(3)) == 3
659+
@test a == BigInt(3)
660+
661+
@test Base.GMP.MPQ.set_ui(1, 2) == 1//2
662+
@test Base.GMP.MPQ.set_ui(0, 1) == 0//1
663+
@test Base.GMP.MPQ.set_ui!(a, 1, 2) == 1//2
664+
@test a == 1//2
665+
666+
@test Base.GMP.MPQ.set_si(1, 2) == 1//2
667+
@test Base.GMP.MPQ.set_si(-1, 2) == -1//2
668+
@test Base.GMP.MPQ.set_si!(a, -1, 2) == -1//2
669+
@test a == -1//2
670+
end
671+
672+
@testset "infinities" begin
673+
oz = Rational{BigInt}(1, 0)
674+
zo = Rational{BigInt}(0, 1)
675+
oo = Rational{BigInt}(1, 1)
676+
677+
@test Base.GMP.MPQ.add!(zo, oz) == oz
678+
@test zo == oz
679+
zo = Rational{BigInt}(0, 1)
680+
681+
@test Base.GMP.MPQ.sub!(zo, oz) == -oz
682+
@test zo == -oz
683+
zo = Rational{BigInt}(0, 1)
684+
685+
@test Base.GMP.MPQ.add!(zo, -oz) == -oz
686+
@test zo == -oz
687+
zo = Rational{BigInt}(0, 1)
688+
689+
@test Base.GMP.MPQ.sub!(zo, -oz) == oz
690+
@test zo == oz
691+
zo = Rational{BigInt}(0, 1)
692+
693+
@test Base.GMP.MPQ.mul!(-oz, -oz) == oz
694+
@test Base.GMP.MPQ.mul!(-oz, oz) == -oz
695+
@test Base.GMP.MPQ.mul!(oz, -oz) == -1//0
696+
@test oz == -1//0
697+
oz = Rational{BigInt}(1, 0)
698+
699+
@test Base.GMP.MPQ.div!(oo, zo) == oz
700+
@test oo == oz
701+
oo = Rational{BigInt}(1, 1)
702+
703+
@test Base.GMP.MPQ.div!(-oo, zo) == -oz
704+
end
705+
end

0 commit comments

Comments
 (0)