From 0735854ab3379f2139d0207df52f5921a9a58a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Ahlb=C3=A4ck?= Date: Thu, 25 Apr 2024 16:35:48 +0200 Subject: [PATCH] Make gcdx(0, 0) return (0, 0, 0) (#40989) As the title suggests. This should conform to GMP's definition: https://gmplib.org/manual/Number-Theoretic-Functions#index-Extended-GCD. --- NEWS.md | 2 + base/gmp.jl | 5 - base/intfuncs.jl | 1 + base/rational.jl | 2 +- test/intfuncs.jl | 246 ++++++++++++++++++++++++----------------------- test/rational.jl | 2 +- 6 files changed, 133 insertions(+), 125 deletions(-) diff --git a/NEWS.md b/NEWS.md index 72b4629fe4174..168a2d03293d4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/base/gmp.jl b/base/gmp.jl index a7caa8dfcdafd..1d188af0e5a93 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -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 diff --git a/base/intfuncs.jl b/base/intfuncs.jl index a9ed1694b7bc0..90aba9ef5564c 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -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 diff --git a/base/rational.jl b/base/rational.jl index bd1633bd3dd28..00a630c396c8c 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -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) diff --git a/test/intfuncs.jl b/test/intfuncs.jl index ed661b2806fb5..bdfbe42bad0bd 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -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) @@ -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) @@ -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}) @@ -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) diff --git a/test/rational.jl b/test/rational.jl index b59b25c20656e..c6f81372de0b9 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -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))