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

check for typemin(T) in denominator of rational #32572

Merged
merged 9 commits into from
Dec 17, 2019
8 changes: 6 additions & 2 deletions base/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ struct Rational{T<:Integer} <: Real
den::T

function Rational{T}(num::Integer, den::Integer) where T<:Integer
num == den == zero(T) && __throw_rational_argerror(T)
num == den == zero(T) && __throw_rational_argerror_zero(T)
if T <: Signed && hasmethod(typemin, Tuple{Type{T}})
simeonschaub marked this conversation as resolved.
Show resolved Hide resolved
den == typemin(T) && __throw_rational_argerror_typemin(T)
Copy link
Member

Choose a reason for hiding this comment

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

I'm sorry, in my previous review I was just looking at the typemin stuff, but actually I think this error logic here is not appropriate in general. A type could have a typemin without the current definition being an error. The real problem occurs when typemin(T) == -typemin(T) (which is typically true for bit types which use 2-complement arithmetic). So I suggest instead moving the error checking in the below line, something like: T <: Signed && signbit(den) && signbit(-den) && throw(...).

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, makes sense. So just delete all the hastypemin stuff?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think, this check is very good, because it requires construction of -den, which is expensive in the case of BigInt.

Copy link
Member

Choose a reason for hiding this comment

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

It can be constructed only when necessary.

end
num2, den2 = signbit(den) ? divgcd(-num, -den) : divgcd(num, den)
Copy link
Member

Choose a reason for hiding this comment

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

As @KlausC suggested, this constructs unnecessarily -den once more than necessary. You can rearrange the code to fix that, e.g.

num2, den2 = if signbit(den)
    den = -den
    T <: Signed && signbit(den) && __throw_rational_argerror_typemin(T)
    divgcd(-num, den)
else
    divgcd(num, den)
end

new(num2, den2)
end
end
@noinline __throw_rational_argerror(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)"))
@noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)"))
@noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)"))

Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n,d)
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
Expand Down
2 changes: 2 additions & 0 deletions test/rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ using Test
@test_throws ArgumentError rationalize(Int, big(3.0), -1.)
# issue 26823
@test_throws InexactError rationalize(Int, NaN)
# issue 32569
@test_throws ArgumentError 1 // typemin(Int)

for a = -5:5, b = -5:5
if a == b == 0; continue; end
Expand Down