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 gcdx(0, 0) return (0, 0, 0) #40989

Merged
merged 4 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading