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

RFC: Calculations producing invalid rationals throw ArgumentError #25702

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

omus
Copy link
Member

@omus omus commented Jan 23, 2018

Previously a calculation such as (0//1) / 0 would result in a DivideError which does not adequately inform the user that 0//0 is not a constructible rational. Now an ArgumentError occurs which states "invalid rational: zero(Int)//zero(Int)".

This change should allow errors from more complicated operations like std([1//1]) be slightly easier to understand.

Before PR:

julia> std([1//1])
ERROR: DivideError: integer division error
Stacktrace:
 [1] div at ./int.jl:229 [inlined]
 [2] divgcd(::Int64, ::Int64) at ./rational.jl:24
 [3] //(::Rational{Int64}, ::Rational{Int64}) at ./rational.jl:51
 [4] / at ./rational.jl:255 [inlined]
 [5] / at ./promotion.jl:293 [inlined]
 [6] #varm#669(::Bool, ::Function, ::Array{Rational{Int64},1}, ::Rational{Int64}) at ./statistics.jl:171
 [7] #varm at ./<missing>:0 [inlined]
 [8] #var#674 at ./statistics.jl:202 [inlined]
 [9] #var at ./<missing>:0 [inlined]
 [10] #std#678(::Bool, ::Nothing, ::Function, ::Array{Rational{Int64},1}) at ./statistics.jl:263
 [11] std(::Array{Rational{Int64},1}) at ./statistics.jl:263
 [12] top-level scope

After PR:

julia> std([1//1])
ERROR: ArgumentError: invalid rational: zero(Int64)//zero(Int64)
Stacktrace:
 [1] Type at ./rational.jl:13 [inlined]
 [2] Type at ./rational.jl:18 [inlined]
 [3] // at ./rational.jl:40 [inlined]
 [4] //(::Rational{Int64}, ::Rational{Int64}) at ./rational.jl:53
 [5] / at ./rational.jl:255 [inlined]
 [6] / at ./promotion.jl:293 [inlined]
 [7] #varm#669(::Bool, ::Function, ::Array{Rational{Int64},1}, ::Rational{Int64}) at ./statistics.jl:171
 [8] #varm at ./<missing>:0 [inlined]
 [9] #var#674 at ./statistics.jl:202 [inlined]
 [10] #var at ./<missing>:0 [inlined]
 [11] #std#678(::Bool, ::Nothing, ::Function, ::Array{Rational{Int64},1}) at ./statistics.jl:263
 [12] std(::Array{Rational{Int64},1}) at ./statistics.jl:263
 [13] top-level scope

Related to: #25300

@omus omus added the rationals The Rational type and values thereof label Jan 23, 2018
@StefanKarpinski
Copy link
Member

A few observations.

  1. this just makes (0//1)//0 behave the same as 0//0 which seems like an obvious thing to do. This is the current, inconsistent behavior:
julia> 0//0
ERROR: ArgumentError: invalid rational: zero(Int64)//zero(Int64)
Stacktrace:
 [1] Type at ./rational.jl:13 [inlined]
 [2] Type at ./rational.jl:18 [inlined]
 [3] //(::Int64, ::Int64) at ./rational.jl:40
 [4] top-level scope

julia> (0//1)//0
ERROR: DivideError: integer division error
Stacktrace:
 [1] div at ./int.jl:229 [inlined]
 [2] divgcd(::Int64, ::Int64) at ./rational.jl:24
 [3] //(::Rational{Int64}, ::Int64) at ./rational.jl:43
 [4] top-level scope
  1. it seems like divgcd's behavior is at the root of this – perhaps divgcd(0, 0) should be defined to return (0, 0), regardless of argument types?

  2. the divgcd function seems generally useful, perhaps we should export, document, and test it? My understanding is that at a semantic level it reduces its arguments to coprime factors by dividing out all common factors, preserving sign.

@omus
Copy link
Member Author

omus commented Jan 23, 2018

Looks like somehow this change makes Base.promote_op(/, Rational{Int64}, Complex{UInt64}) return Any. I'll investigate.

@omus
Copy link
Member Author

omus commented Jan 24, 2018

I'm unsure where the the issue is coming from. Will probably not have time to investigate for a little while.

@JeffBezanson
Copy link
Member

The fix for that is probably to delete promote_op.

omus added 2 commits February 13, 2018 08:38
Previously a calculation such as `(0//1) / 0` would result in a
`DivideError` which does not adequately inform the user that `0//0` is
not a constructable rational. Now an `ArgumentError` occurs which states
"invalid rational: zero(Int)//zero(Int)".

This change should allow errors from more complicated operations like
`std([1//1])` be easier to understand.
@omus omus force-pushed the cv/invalid-rational branch from 1b91f72 to ec18ebd Compare February 13, 2018 14:44
@omus
Copy link
Member Author

omus commented Feb 13, 2018

The problem ended up being me not returning the correct type when the gcd was zero.

@omus
Copy link
Member Author

omus commented Feb 14, 2018

Adding the triage label to ensure that this is ok to merge.

@omus omus added the triage This should be discussed on a triage call label Feb 14, 2018
g = gcd(x, y)
if iszero(g)
Tx, Ty, Tg = typeof(x), typeof(y), typeof(g)
return zero(promote_op(div, Tx, Tg)), zero(promote_op(div, Ty, Tg))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd think zero(div(x, one(g))) should work here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greatest common (positive) divisor (or zero if x and y are both zero).

@JeffBezanson
Copy link
Member

I'm not totally convinced this needs to be changed. Another option is to simplify things even further by removing the special invalid rational error, and always giving a DivideError.

@StefanKarpinski StefanKarpinski removed the triage This should be discussed on a triage call label Feb 15, 2018
oscardssmith pushed a commit that referenced this pull request Feb 7, 2024
Fixes #51731 and #51730 

Also fixes one of the previously broken tests:
```julia
@test_broken Rational{Int64}(UInt(1), typemin(Int32)) == Int64(1) // Int64(typemin(Int32))
```

This PR ensures the `Rational{T}` constructor with concrete `T` will
only throw if the final numerator and denominator cannot be represented
by `T`, or are both zero.

If the `T` in `Rational{T}` is not concrete, this PR tries to ensure the
numerator and denominator are promoted to the same type.
This means `-1*Rational{Integer}(-1, 0x01) == 1` doesn't throw now. A
side effect of this is that `Rational{Integer}(Int8(-1), 0x01)` now
throws.

Also, related to
<#25702 (comment)>,
now `divgcd` doesn't change the types of its inputs and can handle
`typemin`, but it still throws a `DivideError` if both inputs are zero.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rationals The Rational type and values thereof
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants