Skip to content

Commit 2907ebd

Browse files
LiozouKristofferC
authored andcommitted
Fix gcdx and lcm with mixed signed/unsigned arguments (#59628)
Add `gcdx(a::Signed, b::Unsigned)` and `gcdx(a::Unsigned, b::Signed)` methods to fix #58025: ```julia julia> gcdx(UInt16(100), Int8(-101)) # pr (0x0001, 0xffff, 0xffff) julia> gcdx(UInt16(100), Int8(-101)) # master, incorrect result (0x0005, 0xf855, 0x0003) ``` Also add the equivalent methods for `lcm` to fix the systematic `InexactError` when one argument is a negative `Signed` and the other is any `Unsigned`: ```julia julia> lcm(UInt16(100), Int8(-101)) # pr 0x2774 julia> lcm(UInt16(100), Int8(-101)) # master, error ERROR: InexactError: trunc(UInt16, -101) Stacktrace: [1] throw_inexacterror(func::Symbol, to::Type, val::Int8) @ Core ./boot.jl:866 [2] check_sign_bit @ ./boot.jl:872 [inlined] [3] toUInt16 @ ./boot.jl:958 [inlined] [4] UInt16 @ ./boot.jl:1011 [inlined] [5] convert @ ./number.jl:7 [inlined] [6] _promote @ ./promotion.jl:379 [inlined] [7] promote @ ./promotion.jl:404 [inlined] [8] lcm(a::UInt16, b::Int8) @ Base ./intfuncs.jl:152 [9] top-level scope @ REPL[62]:1 ``` Inspired by #59487 (comment). The difference is that the solution proposed in this PR keeps the current correct result type for inputs such as `(::Int16, ::UInt8)`. (cherry picked from commit 4f1e471)
1 parent 0c1aa8d commit 2907ebd

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

base/intfuncs.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ gcd(a::Rational) = checked_abs(a.num) // a.den
148148
lcm(a::Union{Integer,Rational}) = gcd(a)
149149
gcd(a::Unsigned, b::Signed) = gcd(promote(a, abs(b))...)
150150
gcd(a::Signed, b::Unsigned) = gcd(promote(abs(a), b)...)
151+
lcm(a::Unsigned, b::Signed) = lcm(promote(a, abs(b))...)
152+
lcm(a::Signed, b::Unsigned) = lcm(promote(abs(a), b)...)
151153
gcd(a::Real, b::Real) = gcd(promote(a,b)...)
152154
lcm(a::Real, b::Real) = lcm(promote(a,b)...)
153155
gcd(a::Real, b::Real, c::Real...) = gcd(a, gcd(b, c...))
@@ -252,6 +254,16 @@ function gcdx(a::Real, b::Real, cs::Real...)
252254
d′, x, ys... = gcdx(d, cs...)
253255
return d′, i*x, j*x, ys...
254256
end
257+
function gcdx(a::Signed, b::Unsigned)
258+
R = promote_type(typeof(a), typeof(b))
259+
_a = a % signed(R) # handle the case a == typemin(typeof(a)) if R != typeof(a)
260+
d, u, v = gcdx(promote(abs(_a), b)...)
261+
d, flipsign(u, a), v
262+
end
263+
function gcdx(a::Unsigned, b::Signed)
264+
d, v, u = gcdx(b, a)
265+
d, u, v
266+
end
255267

256268
# multiplicative inverse of n mod m, error if none
257269

test/intfuncs.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,35 @@ end
216216
x, y = Int8(-12), UInt(100)
217217
d, u, v = gcdx(x, y)
218218
@test x*u + y*v == d
219+
220+
end
221+
222+
# issue #58025
223+
@testset "Mixed signed/unsigned types" begin
224+
cases = [ # adapted from https://github.com/JuliaLang/julia/pull/59487#issuecomment-3258209203
225+
(UInt16(100), Int8(-101)),
226+
(Int8(-50), UInt16(75)),
227+
(UInt32(12), Int16(-18)),
228+
(Int64(-24), UInt8(36)),
229+
(UInt8(15), Int16(-25)),
230+
(Int32(-42), UInt64(56)),
231+
(UInt128(1000), Int32(-1500)),
232+
(UInt64(0), Int32(-5)),
233+
(Int16(-7), UInt8(0)),
234+
(Int8(-14), UInt8(13)),
235+
]
236+
for (a, b) in cases
237+
g1 = gcd(a, b)
238+
g2, s, t = gcdx(a, b)
239+
@test g1 === g2
240+
@test s*a + t*b == g2
241+
@test g2 >= 0
242+
@test lcm(a, b) === convert(typeof(g1), lcm(widen(a), widen(b)))
243+
end
244+
245+
@test gcdx(Int16(-32768), Int8(-128)) === (Int16(128), Int16(0), Int16(-1))
246+
@test gcdx(Int8(-128), UInt16(256)) === (0x0080, 0xffff, 0x0000)
247+
@test_broken gcd(Int8(-128), UInt16(256)) === 0x0080
219248
end
220249

221250
@testset "gcd/lcm/gcdx for custom types" begin

0 commit comments

Comments
 (0)