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

fix hypot with multiple arguments #38158

Merged
merged 1 commit into from
Oct 30, 2020
Merged

Conversation

vtjnash
Copy link
Member

@vtjnash vtjnash commented Oct 23, 2020

Fixes #27141: The previous code led to under/overflow.
Rebase from #30301 (no permissions to update that directly).
Seems to remove the special cases for Number types added in #34817, but seems to still be passing the tests.

@@ -1150,12 +1150,7 @@ for func in (:sin,:cos,:tan,:asin,:acos,:atan,:sinh,:cosh,:tanh,:asinh,:acosh,
end
end

for func in (:atan,:hypot)
@eval begin
$func(a::Float16,b::Float16) = Float16($func(Float32(a),Float32(b)))
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we still use this for hypot of `Float16?

Copy link
Member Author

@vtjnash vtjnash Oct 26, 2020

Choose a reason for hiding this comment

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

We could, though as the PR was written, it would have been the wrong function to overload (since we want to always give it once chance to call float, then give it once chance to dispatch on the result, even if the type of the answer is the unchaged). It would be possible to define:

_hypot(a::Float16, b::Float16) = Float16(_hypot(Float32(a), Float32(b)))
_hypot(xs::Float16...) = Float16(_hypot(Float32.(xs)...))

But this also fall out of the generic definition _hypot.

Copy link
Member

Choose a reason for hiding this comment

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

This indeed seems to have broken things #38311 (comment)

@test (@inferred hypot(Furlong(1), Furlong(1), Furlong(1))) == Furlong(sqrt(3))
@test (@inferred hypot(Furlong(Inf), Furlong(NaN), Furlong(0))) == Furlong(Inf)
@test (@inferred hypot(Furlong(Inf), Furlong(Inf), Furlong(Inf))) == Furlong(Inf)
@test isnan(hypot(Furlong(NaN), Furlong(0), Furlong(1)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Can there be a test hypot(Furlong(1), 1) etc. to check that an error is thrown? I think this might fail currently.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is what it does currently (master and PR):

julia> hypot(Furlongs.Furlong(1), 1)
ERROR: promotion of types Main.Furlongs.Furlong{1, Int64} and Int64 failed to change any arguments
Stacktrace:
 [1] error(::String, ::String, ::String)
   @ Base ./error.jl:42
 [2] sametype_error(input::Tuple{Main.Furlongs.Furlong{1, Int64}, Int64})
   @ Base ./promotion.jl:316
 [3] not_sametype(x::Tuple{Main.Furlongs.Furlong{1, Int64}, Int64}, y::Tuple{Main.Furlongs.Furlong{1, Int64}, Int64})
   @ Base ./promotion.jl:310
 [4] promote
   @ ./promotion.jl:293 [inlined]
 [5] hypot(::Main.Furlongs.Furlong{1, Int64}, ::Int64)
   @ Base.Math ./math.jl:641
 [6] top-level scope
   @ REPL[5]:1

And I added a test for it also to make sure that continues to operate the same. Is that what you were intending?

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the test and for explaining that this PR doesn't change the error behavior of hypot(x, y) for disparate dimensions. I think it would be better for a user if hypot throws an error that has to do with a failure to compute on disparate dimensions instead of a promotion error, but that's not the point of this PR I suppose.

I still think it's the case that _hypot(Furlongs.Furlong(1), 1) will produce a meaningless result instead of an error. Perhaps that should be documented, even though this function should be not used externally.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like that also still fails, since we need to find the maximum value:

julia> Base.Math._hypot(Furlongs.Furlong(1), 1)
ERROR: MethodError: no method matching isless(::Main.Furlongs.Furlong{0, Float64}, ::Float64)
Closest candidates are:
  isless(::Main.Furlongs.Furlong{p, T} where T<:Number, ::Main.Furlongs.Furlong{p, T} where T<:Number) where p at /Users/jameson/julia/test/testhelpers/Furlongs.jl:64
  isless(::Any, ::Missing) at missing.jl:88
  isless(::Missing, ::Any) at missing.jl:87
  ...
Stacktrace:
 [1] <(x::Main.Furlongs.Furlong{0, Float64}, y::Float64)
   @ Base ./operators.jl:279
 [2] >(x::Float64, y::Main.Furlongs.Furlong{0, Float64})
   @ Base ./operators.jl:305
 [3] _hypot(x::Main.Furlongs.Furlong{1, Int64}, y::Int64)
   @ Base.Math ./math.jl:657
 [4] top-level scope
   @ REPL[2]:1

I can require them to have the same type via the method signature, but I'm more inclined to let the user call Math._hypot directly if they want to, and leave the error checking to the public hypot function.

Fixes JuliaLang#27141: The previous code led to under/overflow.
@vtjnash vtjnash merged commit 479f4f1 into JuliaLang:master Oct 30, 2020
@vtjnash vtjnash deleted the fix_hypot branch October 30, 2020 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Vararg version of hypot does overflow and underflow on master
4 participants