-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Correction of lcm() for Array arguments #56113
base: master
Are you sure you want to change the base?
Conversation
The init argument in the reduce() function was incorrect. It is sufficient to initialize with any value in the Array.
Learn to do a PR: Correction of the gcd() and lcm() of Array.
This comment was marked as resolved.
This comment was marked as resolved.
@cyanescent thanks for your contribution! When you propose a change which includes a bugfix, could you please always add a relevant test, to demonstrate the bug is indeed fixed and there won't be regressions in the future? Thanks! |
Interesting, I guess they have not implemented it for In my understanding, the In practice, we have just seen that EDIT: it could be a cleaner error though. The proposed patch throws trying to evaluate an empty array at index 1. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
That is true for integers, but not for Rationals: julia> lcm(1//2,1)
1//1
julia> gcd(1//2,1)
1//2 I have just tried FriCAS, and I am very puzzled by it's seemingly wrong behaviour for rationals:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test needs to be adjusted or removed: Line 181 in 159adbf
|
Co-authored-by: Neven Sajko <s@purelymail.com>
@cyanescent: I just realized you made this PR from your master branch. The correct procedure is to create a new branch for each PR (feature branches). Could you do either of these:
|
@@ -149,8 +155,8 @@ lcm(a::Real, b::Real, c::Real...) = lcm(a, lcm(b, c...)) | |||
gcd(a::T, b::T) where T<:Real = throw(MethodError(gcd, (a,b))) | |||
lcm(a::T, b::T) where T<:Real = throw(MethodError(lcm, (a,b))) | |||
|
|||
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc; init=zero(eltype(abc))) | |||
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc; init=one(eltype(abc))) | |||
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the change for lcm
, but why also change this in gcd
, it works fine, doesn't it?
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc) | |
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc; init=zero(eltype(abc))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but does it make sense?
EDIT: it makes sense if we also define:
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc; init=1//zero(eltype(abc)))
or just lcm(T[]) = 1//zero(T)
, to prevent returning Vector{Rational}
when the input is Vector{Int}
for non-empty arrays.
cf. #55379 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gcd(T[]) = zero(T)
is correct.
From the docstring,
Greatest common (positive) divisor (or zero if all arguments are zero).
In this case, all arguments are zero (vacuously) so the answer is explicitly documented to be zero.
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc; init=zero(eltype(abc))) | ||
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc; init=one(eltype(abc))) | ||
gcd(abc::AbstractArray{<:Real}) = reduce(gcd, abc) | ||
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc) | |
lcm(abc::AbstractArray{<:Integer}) = reduce(lcm, abc, init=one(eltype(abc))) | |
lcm(abc::AbstractArray{<:Real}) = reduce(lcm, abc) |
For integers, 1 is an identity of LCM (because all integers are a multiple of 1) and is also the correct answer for the empty case (see #55379 (comment)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for finding and fixing this bug, @cyanescent!
Right now, according to my understanding of the documented behavior, this PR makes lcm
& gcd
throw more than they need to which is breaking, so we'll need to address that before merging.
Specifically, this makes gcd(Rational{Int}[])
and lcm(Int[])
throw when the previously returned answers (0//1
and 1
) were correct.
@cyanescent are you still interested in this? Have you tried creating a new PR with a proper "feature" branch? |
OK, I hope this won't confuse NanoSoldier, then. Here are the tests I wrote: @testset "`gcd`, `lcm` binary operation properties" begin
function test_commutativity(f, a, b)
@test f(a, b) == f(b, a)
end
function test_product_property(f, g, a, b)
@test f(a, b) * g(a, b) == abs(a * b)
end
function test_thomaes_function_gcd(f, a, b)
@test f(a, b) == (abs(a) // denominator(b // a))
end
function gcd_array(a, b)
gcd([a, b])
end
function lcm_array(a, b)
lcm([a, b])
end
tested_integers = setdiff(-7:7, 0)
for (f, g) ∈ ((gcd, lcm), (gcd_array, lcm_array))
for a ∈ tested_integers
for b ∈ tested_integers
test_commutativity(f, a, b)
test_commutativity(g, a, b)
test_product_property(f, g, a, b)
test_thomaes_function_gcd(f, a, b)
end
end
for an ∈ tested_integers
for ad ∈ tested_integers
a = an // ad
for bn ∈ tested_integers
for bd ∈ tested_integers
b = bn // bd
test_commutativity(f, a, b)
test_commutativity(g, a, b)
test_product_property(f, g, a, b)
test_thomaes_function_gcd(f, a, b)
end
end
end
end
end
end Probably also something like: @testset "`lcm` of empty" begin
T = Rational{Int}
for emp ∈ (Vector{T}(undef, 0), Matrix{T}(undef, 0, 0))
@test_throws ArgumentError lcm(emp)
end
end |
This is a draft (1st PR attempt!) to try to correct the wrong implementation of the
lcm()
function with Array argument, cf. bug #55379.The
init
argument in thereduce()
function was incorrect. It is sufficient toinitialize with any value in the Arrayremove theinit
argument.I have also added examples in the doc string, but I don't know if I did it the right way (Array argument may need a separate entry). Also, I don't know yet how to write a test.
Fixes #55379
Fixes #56166