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

Conversation

simeonschaub
Copy link
Member

fix #32569. This is my first PR here, I hope, I didn't miss anything.

base/rational.jl Outdated Show resolved Hide resolved
@KlausC
Copy link
Contributor

KlausC commented Jul 24, 2019

I am not happy with your use of isbitstype(T); please replace with Base.hastypemax(T). That is also the tenor of @rfourquet's comment, I think.

The former makes use of the reflection interface and produces runtime code like:

julia> @code_llvm isbitstype(Int)
;  @ reflection.jl:452 within isbitstype
define i8 @julia_isbitstype_16522(%jl_value_t addrspace(10)*) {
top:
; ┌ @ Base.jl:15 within getproperty
   %1 = load i8, i8* inttoptr (i64 140103251893833 to i8*), align 1
   %2 = and i8 %1, 1
;
  ret i8 %2
}

while the proposed call evaluates essentially to a constant at run time:

julia> @code_llvm Base.hastypemax(Int)
;  @ intfuncs.jl:726 within `hastypemax'
define i8 @julia_hastypemax_16523(%jl_value_t addrspace(10)*) {
top:
  ret i8 1
}

@simeonschaub
Copy link
Member Author

Are you suggesting that I implement a hastypemin function, analogous to Base.hastypemax, or should I just assume that every Integer that has typemax defined also has typemin defined and just use hastypemax here?

@rfourquet
Copy link
Member

Are you suggesting that I implement a hastypemin function, analogous to Base.hastypemax

Yes

@KlausC
Copy link
Contributor

KlausC commented Jul 26, 2019

The implementation: hastypemin(x::Type{<:Integer}) = hastypemax(x) would cover all subtypes of Integer defined in Base correctly and with the same performance advantage.

BTW: Base.hastypemax is not implemented optimally for floating point types.

@rfourquet
Copy link
Member

The implementation: hastypemin(x::Type{<:Integer}) = hastypemax(x) would cover all subtypes of Integer defined in Base correctly

But it's not valid in general, the definitions are tiny, let's not try to save duplication here at the cost of an approximation

@simeonschaub
Copy link
Member Author

Bump.

@KlausC
Copy link
Contributor

KlausC commented Nov 23, 2019

bump!
The rationals label is missing.

base/rational.jl Outdated
num == den == zero(T) && __throw_rational_argerror(T)
num == den == zero(T) && __throw_rational_argerror_zero(T)
if T <: Signed && hastypemin(T)
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.

@rfourquet rfourquet added bugfix This change fixes an existing bug rationals The Rational type and values thereof labels Nov 25, 2019
@KlausC
Copy link
Contributor

KlausC commented Nov 26, 2019

I think the previous commit was better, because it does not require to construct the negative of a BigInt in that case. Timing examples:

julia> @btime Base.hastypemax(BigInt)
  0.016 ns (0 allocations: 0 bytes)
false

julia> @btime -$(big(1));
  54.518 ns (2 allocations: 40 bytes)

julia> x = big(51)^1000;
julia> @btime -$x;
  119.650 ns (3 allocations: 744 bytes)

base/rational.jl Outdated
# issue #32569
if T <: Signed
signbit(den) && signbit(-den) && __throw_rational_argerror_typemin(T)
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

@KlausC
Copy link
Contributor

KlausC commented Nov 27, 2019

a little polished:

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

@KlausC
Copy link
Contributor

KlausC commented Nov 30, 2019

@StefanKarpinski , what do you think?

base/rational.jl Outdated
num2, den2 = signbit(den) ? divgcd(-num, -den) : divgcd(num, den)
new(num2, den2)
num == den == zero(T) && __throw_rational_argerror_zero(T)
# issue #32569
Copy link
Member

Choose a reason for hiding this comment

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

Usually we don't insert references to issues in code (as opposed to tests), and here I think we can understand the reason of this test by following the thrown error, faster than by looking up github. No really a problem though, and probably not necessary to update your PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

That sounds reasonable. Went ahead and deleted it now.

@KlausC
Copy link
Contributor

KlausC commented Nov 30, 2019

Additional issue? Are these test cases ok now?

@test -2 // typemin(Int) == 1//4611686018427387904
@test 2 // typemin(Int) == -1//4611686018427387904

maybe the divgcd has to be done before the checks.

@KlausC
Copy link
Contributor

KlausC commented Dec 12, 2019

@simeonschaub, would you mind to insert the changes to fix - last comment?

@KlausC
Copy link
Contributor

KlausC commented Dec 12, 2019

I am not sure, if the @show .. should be there.

The test cases are only valid if Int == Int64; should be formulated generically.

Do you run make test-rational locally?

@simeonschaub
Copy link
Member Author

Oh, sorry. My bad! I pushed a bit too prematurely.

@rfourquet
Copy link
Member

Looks like good to go, I will merge in one day or two if no other review comments (does it look good for you now @KlausC ?)

@rfourquet rfourquet merged commit fdcaa06 into JuliaLang:master Dec 17, 2019
@rfourquet
Copy link
Member

Thanks @simeonschaub for the fix and @KlausC for the review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix This change fixes an existing bug rationals The Rational type and values thereof
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rational(1, typemin(Int)) comparisons with 0 and 1 deny transitiveness of <
3 participants