-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Fix behaviour of + and - with rational infinity #16111
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
Conversation
test/numbers.jl
Outdated
| for (xfl, xra) in ((Inf, 1//0), (-Inf, -1//0)), | ||
| (yfl, yra) in ((Inf, 1//0), (-Inf, -1//0)) | ||
| if isnan(op(xfl, yfl)) | ||
| @test_throws Exception op(xra, yra) |
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.
better to be as specific as possible
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.
What kind of exception is most appropriate? Currently it throws either ArgumentError or DivideError depending on what goes wrong first, and I think that could be improved (if the overhead of a try-catch is acceptable, of course). I am thinking that DomainError makes most sense, since if the result would be NaN for floats that would mean the inputs are not in the domain of the function.
|
Upon further inspection, it looks like I looked at the uses for The few exceptions are: This now results in ArgumentError: operation results in invalid rational, even though the return type isn't rational. Whereas still results in DivideError. Same goes for 0 ÷ 0//1 and 1 ÷ 0//1, which now return different errors. They were also returning different errors before (as in, different stack frame causing the error), but this time the error type has changed also. |
|
Here's another conundrum. julia> 1//0 % 0//1DivideError or ArgumentError? We're dividing by 0, but it's not an integer in this case. DivideError or ArgumentError? The docs only say DivideError occurs when dividing by 0 or typemin by -1. Here the division is by infinity. Maybe all operations on rationals should be DivideError. This would require the Rational constructor itself to throw DivideError instead of ArgumentError. |
|
Just as an aside, the fact that it's often so difficult to figure out the "right" exception type is one of the things that makes them sort of suspicious to me. |
|
I'm surprised that +-infinity is allowed for rationals without a corresponding +-0. Why should |
A compromise I guess. It's to mimick floats (double) , such that conversion between floats and rationals are lossless. Then we can convert between |
|
For what it's worth, I think that the signed 0 is a good idea. But using an additional byte of memory just for a signed zero is excessive in my opinion. |
|
The loss on conversion isn't specific to rationals: julia> Int(-0.0)
0Though it looks like it could be a avoided for rationals: instead adding a field for the sign, which would be wasteful, why not use the convention that |
What do you mean? The numerator holds the sign such that |
|
Allowing negative numbers in the denominator only if the numerator numerator is zero is a bit of a hack. We could dispense of the normalization for rationals, and only compute gcds when required (on overflow or when displaying). That could make rational arithmetic faster also. |
|
Yes, that's a hack and I'm not recommending it. I just mentioned it as an alternative to adding a separate field, which is clearly overkill. |
|
I think what @nalimilan was suggesting is to have Currently we normalize rationals so that the denominator is always non-negative – this would change that. |
e77d229 to
aced02e
Compare
|
I have a solution to the error problem that I like:
I updated the tests with these semantics. Edit: Actually, on second thought, these semantics are incorrect (and the tests for them are incomplete). They fail to cover the I feel like the "correct" semantics are very similar to those in the code right now... if 0//0 is constructed, then the result is |
|
This is still an issue on Julia 0.6, and regrettably the answer to "what's the right exception type to throw" is holding it up. |
|
Throwing an inconsistent exception seems like a fairly separate issue. The most important question is exception or not. |
|
For what case? There is a pretty good argument that |
|
Agree |
|
You should probably rebase this. |
|
I tried to do that using the GitHub interface; I suppose that failed CI. I'll rebase it properly tomorrow. |
f57c6b5 to
110fe56
Compare
|
I've rebased this. I modified a test to use |
|
Is this good to go? It's a bugfix so should be OK for 0.6. |
|
Bump. |
StefanKarpinski
left a 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.
Looks good to me. I left some comments, but I think you should ignore them; we can make those kinds of changes at some point in the future since they're largely stylistic.
| else | ||
| g = gcd(x, y) | ||
| promote(div(x, g), div(y, g)) | ||
| end |
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.
Would it make more sense to promote to a common type first:
divgcd(x::Integer, y::Integer) = divgcd(promote(x, y)...)
function divgcd(x::T, y::T) where T<:Integer
if !iszero(x) || !iszero(y)
g = gcd(x, y)
x ÷= g
y ÷= g
end
return x, y
endThere 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.
It's possibly intentional that things are done this way, since div has special cases for unsigned/signed combinations.
julia> @which 0x1 ÷ -1
div(x::Unsigned, y::Union{Int128, Int16, Int32, Int64, Int8}) in Base at int.jl:160This behavior might require reviewing for desirability/consistency.
| else | ||
| xd, yd = divgcd(x.den, y.den) | ||
| Rational(($chop)(checked_mul(x.num,yd), checked_mul(y.num,xd)), checked_mul(x.den,yd)) | ||
| end |
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.
Likewise, maybe promote first, but then again, maybe that can be another PR.
|
I'm really not the best person to review this, I never use rationals. My suggestion above was just an idea... |
|
Fixed on master (I can't find the specific PR where these were fixed but running the added test here passes, as well as the error in the PR's first message) |
The current behaviour of rational infinities under
+and-differs from both floats and mathematical intuition:I have created a fix and tests to make sure rational infinities behave like floating point ones. This is not a very elegant fix, but I could not figure out a better way.