Skip to content

Commit

Permalink
Make gcdx(0, 0) return (0, 0, 0) (#40989)
Browse files Browse the repository at this point in the history
As the title suggests. This should conform to GMP's definition:
https://gmplib.org/manual/Number-Theoretic-Functions#index-Extended-GCD.
  • Loading branch information
albinahlback committed Apr 25, 2024
1 parent 5994cf7 commit 0735854
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 125 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ New library features
Standard library changes
------------------------

* `gcdx(0, 0)` now returns `(0, 0, 0)` instead of `(0, 1, 0)` ([#40989]).

#### StyledStrings

#### JuliaSyntaxHighlighting
Expand Down
5 changes: 0 additions & 5 deletions base/gmp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,6 @@ end
powermod(x::Integer, p::Integer, m::BigInt) = powermod(big(x), big(p), m)

function gcdx(a::BigInt, b::BigInt)
if iszero(b) # shortcut this to ensure consistent results with gcdx(a,b)
return a < 0 ? (-a,-ONE,b) : (a,one(BigInt),b)
# we don't return the globals ONE and ZERO in case the user wants to
# mutate the result
end
g, s, t = MPZ.gcdext(a, b)
if t == 0
# work around a difference in some versions of GMP
Expand Down
1 change: 1 addition & 0 deletions base/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ julia> gcdx(240, 46)
"""
Base.@assume_effects :terminates_locally function gcdx(a::Integer, b::Integer)
T = promote_type(typeof(a), typeof(b))
a == b == 0 && return (zero(T), zero(T), zero(T))
# a0, b0 = a, b
s0, s1 = oneunit(T), zero(T)
t0, t1 = s1, s0
Expand Down
2 changes: 1 addition & 1 deletion base/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ lcm(x::Rational, y::Rational) = unsafe_rational(lcm(x.num, y.num), gcd(x.den, y.
function gcdx(x::Rational, y::Rational)
c = gcd(x, y)
if iszero(c.num)
a, b = one(c.num), c.num
a, b = zero(c.num), c.num
elseif iszero(c.den)
a = ifelse(iszero(x.den), one(c.den), c.den)
b = ifelse(iszero(y.den), one(c.den), c.den)
Expand Down
246 changes: 128 additions & 118 deletions test/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,44 @@ using Random

is_effect_free(args...) = Core.Compiler.is_effect_free(Base.infer_effects(args...))

(a::T, b::T) where T <: Union{Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128} = a === b
(a::T, b::T) where T <: BigInt = a == b

@testset "gcd/lcm" begin
# All Integer data types take different code paths -- test all
# TODO: Test gcd and lcm for BigInt.
for T in (Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128)
@test gcd(T(3)) === T(3)
@test gcd(T(3), T(5)) === T(1)
@test gcd(T(3), T(15)) === T(3)
@test gcd(T(0), T(15)) === T(15)
@test gcd(T(15), T(0)) === T(15)
for T in (Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt)
@test gcd(T(3)) T(3)
@test gcd(T(3), T(5)) T(1)
@test gcd(T(3), T(15)) T(3)
@test gcd(T(0), T(15)) T(15)
@test gcd(T(15), T(0)) T(15)
if T <: Signed
@test gcd(T(-12)) === T(12)
@test gcd(T(0), T(-15)) === T(15)
@test gcd(T(-15), T(0)) === T(15)
@test gcd(T(3), T(-15)) === T(3)
@test gcd(T(-3), T(-15)) === T(3)
@test gcd(T(-12)) T(12)
@test gcd(T(0), T(-15)) T(15)
@test gcd(T(-15), T(0)) T(15)
@test gcd(T(3), T(-15)) T(3)
@test gcd(T(-3), T(-15)) T(3)
end
@test gcd(T(0), T(0)) === T(0)
@test gcd(T(0), T(0)) T(0)

@test gcd(T(2), T(4), T(6)) === T(2)
@test gcd(T(2), T(4), T(6)) T(2)
if T <: Signed
@test gcd(T(2), T(4), T(-6)) === T(2)
@test gcd(T(2), T(-4), T(-6)) === T(2)
@test gcd(T(-2), T(4), T(-6)) === T(2)
@test gcd(T(-2), T(-4), T(-6)) === T(2)
@test gcd(T(2), T(4), T(-6)) T(2)
@test gcd(T(2), T(-4), T(-6)) T(2)
@test gcd(T(-2), T(4), T(-6)) T(2)
@test gcd(T(-2), T(-4), T(-6)) T(2)
end

@test gcd(typemax(T), T(1)) === T(1)
@test gcd(T(1), typemax(T)) === T(1)
@test gcd(typemax(T), T(0)) === typemax(T)
@test gcd(T(0), typemax(T)) === typemax(T)
@test gcd(typemax(T), typemax(T)) === typemax(T)
@test gcd(typemax(T), typemax(T)-T(1)) === T(1) # gcd(n, n-1) = 1. n and n-1 are always coprime.
if T != BigInt
@test gcd(typemax(T), T(1)) === T(1)
@test gcd(T(1), typemax(T)) === T(1)
@test gcd(typemax(T), T(0)) === typemax(T)
@test gcd(T(0), typemax(T)) === typemax(T)
@test gcd(typemax(T), typemax(T)) === typemax(T)
@test gcd(typemax(T), typemax(T)-T(1)) === T(1) # gcd(n, n-1) = 1. n and n-1 are always coprime.
end

if T <: Signed
if T <: Signed && T != BigInt
@test gcd(-typemax(T), T(1)) === T(1)
@test gcd(T(1), -typemax(T)) === T(1)
@test gcd(-typemax(T), T(0)) === typemax(T)
Expand All @@ -52,7 +56,7 @@ is_effect_free(args...) = Core.Compiler.is_effect_free(Base.infer_effects(args..
@test_throws OverflowError gcd(typemin(T), typemin(T))
@test_throws OverflowError gcd(typemin(T), T(0))
@test_throws OverflowError gcd(T(0), typemin(T))
else
elseif T != BigInt
# For Unsigned Integer types, -typemax(T) == 1.
@test gcd(-typemax(T), T(1)) === T(1)
@test gcd(T(1), -typemax(T)) === T(1)
Expand All @@ -71,83 +75,86 @@ is_effect_free(args...) = Core.Compiler.is_effect_free(Base.infer_effects(args..
@test gcd(T(0), typemin(T)) === T(0)
end

@test lcm(T(0)) === T(0)
@test lcm(T(2)) === T(2)
@test lcm(T(2), T(3)) === T(6)
@test lcm(T(3), T(2)) === T(6)
@test lcm(T(4), T(6)) === T(12)
@test lcm(T(6), T(4)) === T(12)
@test lcm(T(3), T(0)) === T(0)
@test lcm(T(0), T(3)) === T(0)
@test lcm(T(0), T(0)) === T(0)
@test lcm(T(0)) T(0)
@test lcm(T(2)) T(2)
@test lcm(T(2), T(3)) T(6)
@test lcm(T(3), T(2)) T(6)
@test lcm(T(4), T(6)) T(12)
@test lcm(T(6), T(4)) T(12)
@test lcm(T(3), T(0)) T(0)
@test lcm(T(0), T(3)) T(0)
@test lcm(T(0), T(0)) T(0)
if T <: Signed
@test lcm(T(-12)) === T(12)
@test lcm(T(0), T(-4)) === T(0)
@test lcm(T(-4), T(0)) === T(0)
@test lcm(T(4), T(-6)) === T(12)
@test lcm(T(-4), T(-6)) === T(12)
@test lcm(T(-12)) T(12)
@test lcm(T(0), T(-4)) T(0)
@test lcm(T(-4), T(0)) T(0)
@test lcm(T(4), T(-6)) T(12)
@test lcm(T(-4), T(-6)) T(12)
end

@test lcm(T(2), T(4), T(6)) === T(12)
@test lcm(T(2), T(4), T(0)) === T(0)
@test lcm(T(2), T(4), T(6)) T(12)
@test lcm(T(2), T(4), T(0)) T(0)
if T <: Signed
@test lcm(T(2), T(4), T(-6)) === T(12)
@test lcm(T(2), T(-4), T(-6)) === T(12)
@test lcm(T(-2), T(-4), T(-6)) === T(12)
@test lcm(T(-2), T(0), T(-6)) === T(0)
end

@test lcm(typemax(T), T(1)) === typemax(T)
@test lcm(T(1), typemax(T)) === typemax(T)
@test lcm(typemax(T), T(0)) === T(0)
@test lcm(T(0), typemax(T)) === T(0)
@test lcm(typemax(T), typemax(T)) === typemax(T)
@test_throws OverflowError lcm(typemax(T), typemax(T)-T(1)) # lcm(n, n-1) = n*(n-1). Since n and n-1 are always coprime.
@test_throws OverflowError lcm(typemax(T), T(2))

let x = isqrt(typemax(T))+T(1) # smallest number x such that x^2 > typemax(T)
@test lcm(x, x) === x
@test_throws OverflowError lcm(x, x+T(1)) # lcm(n, n+1) = n*(n+1). Since n and n+1 are always coprime.
@test lcm(T(2), T(4), T(-6)) T(12)
@test lcm(T(2), T(-4), T(-6)) T(12)
@test lcm(T(-2), T(-4), T(-6)) T(12)
@test lcm(T(-2), T(0), T(-6)) T(0)
end

if T <: Signed
@test lcm(-typemax(T), T(1)) === typemax(T)
@test lcm(T(1), -typemax(T)) === typemax(T)
@test lcm(-typemax(T), T(0)) === T(0)
@test lcm(T(0), -typemax(T)) === T(0)
@test lcm(-typemax(T), -typemax(T)) === typemax(T)
@test lcm(typemax(T), -typemax(T)) === typemax(T)
@test lcm(-typemax(T), typemax(T)) === typemax(T)

@test_throws OverflowError lcm(typemin(T), T(1))
@test_throws OverflowError lcm(T(1), typemin(T))
@test lcm(typemin(T), T(0)) === T(0)
@test lcm(T(0), typemin(T)) === T(0)
@test_throws OverflowError lcm(typemin(T), typemin(T)+T(1)) # lcm(n, n+1) = n*(n+1).
@test_throws OverflowError lcm(typemin(T), typemin(T))
else
# For Unsigned Integer types, -typemax(T) == 1.
@test lcm(-typemax(T), T(1)) === T(1)
@test lcm(T(1), -typemax(T)) === T(1)
@test lcm(-typemax(T), T(0)) === T(0)
@test lcm(T(0), -typemax(T)) === T(0)
@test lcm(-typemax(T), -typemax(T)) === T(1)
@test lcm(-typemax(T), typemax(T)) === typemax(T)
@test lcm(typemax(T), -typemax(T)) === typemax(T)
if T != BigInt
@test lcm(typemax(T), T(1)) === typemax(T)
@test lcm(T(1), typemax(T)) === typemax(T)
@test lcm(typemax(T), T(0)) === T(0)
@test lcm(T(0), typemax(T)) === T(0)
@test lcm(typemax(T), typemax(T)) === typemax(T)
@test_throws OverflowError lcm(typemax(T), typemax(T)-T(1)) # lcm(n, n-1) = n*(n-1). Since n and n-1 are always coprime.
@test_throws OverflowError lcm(typemax(T), T(2))

let x = isqrt(typemax(T))+T(1) # smallest number x such that x^2 > typemax(T)
@test lcm(x, x) === x
@test_throws OverflowError lcm(x, x+T(1)) # lcm(n, n+1) = n*(n+1). Since n and n+1 are always coprime.
end

# For Unsigned Integer types, typemin(T) == 0.
@test lcm(typemin(T), T(1)) === lcm(T(0), T(1)) === T(0)
@test lcm(T(1), typemin(T)) === T(0)
@test lcm(typemin(T), T(0)) === T(0)
@test lcm(T(0), typemin(T)) === T(0)
@test lcm(typemin(T), typemin(T)) === T(0)
@test lcm(typemin(T), typemin(T)+T(1)) === T(0)
if T <: Signed
@test lcm(-typemax(T), T(1)) === typemax(T)
@test lcm(T(1), -typemax(T)) === typemax(T)
@test lcm(-typemax(T), T(0)) === T(0)
@test lcm(T(0), -typemax(T)) === T(0)
@test lcm(-typemax(T), -typemax(T)) === typemax(T)
@test lcm(typemax(T), -typemax(T)) === typemax(T)
@test lcm(-typemax(T), typemax(T)) === typemax(T)

@test_throws OverflowError lcm(typemin(T), T(1))
@test_throws OverflowError lcm(T(1), typemin(T))
@test lcm(typemin(T), T(0)) === T(0)
@test lcm(T(0), typemin(T)) === T(0)
@test_throws OverflowError lcm(typemin(T), typemin(T)+T(1)) # lcm(n, n+1) = n*(n+1).
@test_throws OverflowError lcm(typemin(T), typemin(T))
else
# For Unsigned Integer types, -typemax(T) == 1.
@test lcm(-typemax(T), T(1)) === T(1)
@test lcm(T(1), -typemax(T)) === T(1)
@test lcm(-typemax(T), T(0)) === T(0)
@test lcm(T(0), -typemax(T)) === T(0)
@test lcm(-typemax(T), -typemax(T)) === T(1)
@test lcm(-typemax(T), typemax(T)) === typemax(T)
@test lcm(typemax(T), -typemax(T)) === typemax(T)

# For Unsigned Integer types, typemin(T) == 0.
@test lcm(typemin(T), T(1)) === lcm(T(0), T(1)) === T(0)
@test lcm(T(1), typemin(T)) === T(0)
@test lcm(typemin(T), T(0)) === T(0)
@test lcm(T(0), typemin(T)) === T(0)
@test lcm(typemin(T), typemin(T)) === T(0)
@test lcm(typemin(T), typemin(T)+T(1)) === T(0)
end
end
end
@test lcm(0x5, 3) == 15
@test gcd(0xf, 20) == 5
@test gcd(UInt32(6), Int8(-50)) == 2
@test gcd(typemax(UInt), -16) == 1
@test gcd(typemax(UInt), BigInt(1236189723689716298376189726398761298361892)) == 1

@testset "effects" begin
@test is_effect_free(gcd, Tuple{Int,Int})
Expand All @@ -156,45 +163,48 @@ is_effect_free(args...) = Core.Compiler.is_effect_free(Base.infer_effects(args..
end

@testset "gcd/lcm for arrays" begin
# TODO: Test gcd and lcm for BigInt arrays.
for T in (Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128)
@test gcd(T[]) === T(0)
@test gcd(T[3, 5]) === T(1)
@test gcd(T[3, 15]) === T(3)
@test gcd(T[0, 15]) === T(15)
for T in (Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt)
@test gcd(T[]) T(0)
@test gcd(T[3, 5]) T(1)
@test gcd(T[3, 15]) T(3)
@test gcd(T[0, 15]) T(15)
if T <: Signed
@test gcd(T[-12]) === T(12)
@test gcd(T[3,-15]) === T(3)
@test gcd(T[-3,-15]) === T(3)
@test gcd(T[-12]) T(12)
@test gcd(T[3,-15]) T(3)
@test gcd(T[-3,-15]) T(3)
end
@test gcd(T[0, 0]) === T(0)
@test gcd(T[0, 0]) T(0)

@test gcd(T[2, 4, 6]) === T(2)
@test gcd(T[2, 4, 3, 5]) === T(1)
@test gcd(T[2, 4, 6]) T(2)
@test gcd(T[2, 4, 3, 5]) T(1)

@test lcm(T[]) === T(1)
@test lcm(T[2, 3]) === T(6)
@test lcm(T[4, 6]) === T(12)
@test lcm(T[3, 0]) === T(0)
@test lcm(T[0, 0]) === T(0)
@test lcm(T[]) T(1)
@test lcm(T[2, 3]) T(6)
@test lcm(T[4, 6]) T(12)
@test lcm(T[3, 0]) T(0)
@test lcm(T[0, 0]) T(0)
if T <: Signed
@test lcm(T[-2]) === T(2)
@test lcm(T[4, -6]) === T(12)
@test lcm(T[-4, -6]) === T(12)
@test lcm(T[-2]) T(2)
@test lcm(T[4, -6]) T(12)
@test lcm(T[-4, -6]) T(12)
end

@test lcm(T[2, 4, 6]) === T(12)
@test lcm(T[2, 4, 6]) T(12)
end
end

(a::Tuple{T, T, T}, b::Tuple{T, T, T}) where T <: Union{Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128} = a === b
(a::Tuple{T, T, T}, b::Tuple{T, T, T}) where T <: BigInt = a == b
@testset "gcdx" begin
# TODO: Test gcdx for BigInt.
for T in (Int8, Int16, Int32, Int64, Int128)
@test gcdx(T(5), T(12)) === (T(1), T(5), T(-2))
@test gcdx(T(5), T(-12)) === (T(1), T(5), T(2))
@test gcdx(T(-5), T(12)) === (T(1), T(-5), T(-2))
@test gcdx(T(-5), T(-12)) === (T(1), T(-5), T(2))
@test gcdx(T(-25), T(-4)) === (T(1), T(-1), T(6))
for T in (Int8, Int16, Int32, Int64, Int128, BigInt)
@test gcdx(T(5), T(12)) (T(1), T(5), T(-2))
@test gcdx(T(5), T(-12)) (T(1), T(5), T(2))
@test gcdx(T(-5), T(12)) (T(1), T(-5), T(-2))
@test gcdx(T(-5), T(-12)) (T(1), T(-5), T(2))
@test gcdx(T(-25), T(-4)) (T(1), T(-1), T(6))
@test gcdx(T(0), T(0)) (T(0), T(0), T(0))
@test gcdx(T(8), T(0)) (T(8), T(1), T(0))
@test gcdx(T(0), T(-8)) (T(8), T(0), T(-1))
end
x, y = Int8(-12), UInt(100)
d, u, v = gcdx(x, y)
Expand Down
2 changes: 1 addition & 1 deletion test/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ end
@test gcdx(T(1)//T(1), T(1)//T(0)) === (T(1)//T(0), T(0), T(1))
@test gcdx(T(1)//T(0), T(1)//T(0)) === (T(1)//T(0), T(1), T(1))
@test gcdx(T(1)//T(0), T(0)//T(1)) === (T(1)//T(0), T(1), T(0))
@test gcdx(T(0)//T(1), T(0)//T(1)) === (T(0)//T(1), T(1), T(0))
@test gcdx(T(0)//T(1), T(0)//T(1)) === (T(0)//T(1), T(0), T(0))

if T <: Signed
@test gcdx(T(-1)//T(0), T(1)//T(2)) === (T(1)//T(0), T(1), T(0))
Expand Down

0 comments on commit 0735854

Please sign in to comment.