-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Add Julia ports of hyperbolic (including inverse) functions from openlibm. #24159
Conversation
base/special/hyperbolic.jl
Outdated
# 2. Find the the branch and the expression to calculate and return it | ||
# a) x <= COSH_SMALL_X | ||
# return T(1) | ||
# b) 0 <= x <= ln2/2 |
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.
Maybe COSH_SMALL_X <= x <= ln2/2
? ;-)
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.
That would make sense :)
If these functions are faster than the |
Yes I will! I know I missed it with some of the others. Let's benchmark first :) |
# is preserved. | ||
# ==================================================== | ||
|
||
_ldexp_exp(x::Float64, i::T) where T = ccall(("__ldexp_exp", libm), Float64, (Float64, T), x, i) |
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.
Just a quick comment, this should just use the julia ldexp function, if you don't need the error handling, you can write a _ldexp
function and remove the error/exception handling in ldexp
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.
Oh! I looked for a Julia binding/version.. but I must have mistyped the name when searching.
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.
Is this worth changing now?
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.
Unfortunately, this function is actually not in Julia yet, but yes, it is worth changing, and it should be a simple function to port.
edit: to clarify this requires porting 7-8 lines of code if I recall correctly. It should be in the k_exp.c
file, and I might actually have port locally already.
edit2: yeah, I had them locally. I'll make a pr tomorrow.
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.
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.
no, that computes x*2^n
, what is needed here is exp(n)*2^n
edit: as I said, I'll make a pr tomorrow, as I'm off to bed now, but it goes something like the following, although it will not run because I have not included a few helper functions in the gist.
https://gist.github.com/pkofod/75a237b43cbcb82ea26410aecd8dc4ca
edit2: I know the doc string is wrong for the second function... I'm still mid-PR-preparation, but I'm too tired to continue today
base/special/hyperbolic.jl
Outdated
# acosh methods | ||
ACOSH_LN2(T) = 6.93147180559945286227e-01 | ||
ACOSH_LN2(T) = 6.9314718246e-01 | ||
AH_LN2(T::Type{Float64}) = 6.93147180559945286227e-01 |
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.
No need for T
base/special/hyperbolic.jl
Outdated
@@ -172,9 +176,41 @@ end | |||
tanh(x::Real) = tanh(float(x)) | |||
|
|||
# Inverse hyperbolic functions | |||
# asinh methods | |||
AH_LN2(T) = 6.93147180559945286227e-01 |
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.
Can remove?
|
||
H_LARGE_X(::Type{Float32}) = 88.72283f0 | ||
H_OVERFLOW_X(::Type{Float32}) = 89.415985f0 | ||
function sinh(x::T) where T <: Union{Float32, Float64} |
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.
Wouldn't it be cleaner to use the syntax Oh, nevermind, you use sinh(x::Union{Float32, Float64})
for functions like this?T
in the function body.
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.
yes I use these to pick the branching cutoffs. Do you think it would be cleaner to write sinh(x::Union{Float32, Float64})
and then just define H_LARGE_X(::Float32) = 88.72283f0
? on the actual x
instead of the type of x
? Should compile down to the same thing, right?
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.
I think it's fine as-is.
test/math.jl
Outdated
by = tanh(big(x)) | ||
@test Float32((tanh(x) - by))/eps(abs(Float32(by))) <= 1 | ||
bym = tanh(big(-x)) | ||
@test Float32(abs(tanh(-x) - bym))/eps(abs(Float32(bym))) <= 1 |
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.
No tests for e.g. acosh
?
In general, it would be good to make sure you have test coverage for all of the branches.
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.
Maybe I should have put a wip. All these will get tests and benchmarks, sorry for not making that more clear.
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.
The inverse functions were not there originally, but I decided to add them here and just haven't gotten the tests in yet.
I'm not using the All functions now have tests. Need benchmarks and license-thing. |
benchmark PR JuliaCI/BaseBenchmarks.jl#141 |
If either @simonbyrne or @stevengj have a look at this and approve, it's good to squash merge then! |
test/math.jl
Outdated
end | ||
for x in (pcnfloat(2.7755602085408512e-17)..., pcnfloat(22.0)..., pcnfloat(709.7822265633563)...) | ||
by = cosh(big(x)) | ||
@test Float64((cosh(x) - by))/eps(abs(Float64(by))) <= 1 |
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 be clearer to use @test cosh(x) ≈ cosh(big(y)) rtol=eps(Float64)
here and similarly in the other tests.
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.
big(y)
is meant to be big(x)
, right?
test/math.jl
Outdated
by = acosh(big(x)) | ||
@test Float64((acosh(x) - by))/eps(abs(Float64(by))) <= 1 | ||
end | ||
for x in (nextfloat(1f0), pcnfloat(2f0)..., pcnfloat(2f0^28)...) |
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.
Couldn't you merge these two loops by putting them into the for T
loop as
for x in (nextfloat(T(1)), pcnfloat(T(2))..., pcnfloat(T(2^28))...)
@test acosh(x) ≈ acosh(big(x) rtol=eps(T)
end
test/math.jl
Outdated
by = tanh(big(x)) | ||
@test Float64((tanh(x) - by))/eps(abs(Float64(by))) <= 1 | ||
bym = tanh(big(-x)) | ||
@test Float64(abs(tanh(-x) - bym))/eps(abs(Float64(bym))) <= 1 |
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.
@test tanh(x) ≈ tanh(big(x)) ≈ -tanh(x) rtol=eps(Float64)
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.
You could merge the for x
loops into
for x in Iterators.flatten(pcnfloat.(T == Float64 ? [2.0^-28,1.0,22.0] : [2f0^-12,1f0,9f0]))
@test tanh(x) ≈ tanh(big(x)) ≈ -tanh(x) rtol=eps(T)
end
base/special/hyperbolic.jl
Outdated
acosh(x::Real) = acosh(float(x)) | ||
|
||
# atanh methods | ||
@noinline atanh_domain_error(x) = throw(DomainError(x, "atanh(x) is only defined for x <= 1.")) |
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.
for |x| ≤ 1
base/special/hyperbolic.jl
Outdated
asinh(x::Real) = asinh(float(x)) | ||
|
||
# acosh methods | ||
@noinline acosh_domain_error(x) = throw(DomainError(x, "acosh(x) is only defined for x >= 1.")) |
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.
Unicode ≥
is a bit nicer
Thanks @stevengj ! I hope I've addressed your comments. |
test/math.jl
Outdated
@test sinh(-T(1000)) === -T(Inf) | ||
@test isnan_type(T, sinh(T(NaN))) | ||
end | ||
for x in (pcnfloat(2.0^-28)..., pcnfloat(22.0)..., pcnfloat(709.7822265633563)...) |
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.
Seems like it would be better to use SINH_SMALL_X(T)
, H_MEDIUM_X(T)
, etcetera (importing those symbols into the test). That way it will unify the tests for both T
, make it clearer where these thresholds come from, and ensure the tests are updated if the thresholds change (or if the sinh
code is rewritten).
test/math.jl
Outdated
@test isnan_type(T, sinh(T(NaN))) | ||
end | ||
for x in (pcnfloat(2.0^-28)..., pcnfloat(22.0)..., pcnfloat(709.7822265633563)...) | ||
@test sinh(x) ≈ sinh(big(x)) rtol=eps(Float64) |
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.
Seems simpler to do @test sinh(x) ≈ sinh(big(x)) ≈ -sinh(x) rtol=eps(T)
, but it looks like this syntax isn't supported by @test
. 😢
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.
Yeah, you suggested that above and I tried it but without luck so I just split it out into two.
test/math.jl
Outdated
@test asinh(nextfloat(zero(T))) === nextfloat(zero(T)) | ||
@test asinh(prevfloat(zero(T))) === prevfloat(zero(T)) | ||
@test isnan_type(T, asinh(T(NaN))) | ||
for x in Iterators.flatten(pcnfloat.(T == Float64 ? [2.0^-28,2.0,2.0^28] : [2f0^-12,2f0,22f28])) |
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.
I'm confused about this conditional. Looking back at the asinh
code, it seems like it uses 2^-28
and 2^28
for both single and double precision. 2^-12
is only used for tanh
.
Again, this would be clarified by using TANH_SMALL_X(T)
etcetera here for precision-dependent thresholds.
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.
This is a good idea. Originally it was unsure if I there were going to be tests in separate commits before the actual change (just to make sure nothing changed), but I guess this will be squashed anyway, so might as well just use the thresholds from the actual code.
test/math.jl
Outdated
@@ -907,7 +901,7 @@ end | |||
@test asinh(nextfloat(zero(T))) === nextfloat(zero(T)) | |||
@test asinh(prevfloat(zero(T))) === prevfloat(zero(T)) | |||
@test isnan_type(T, asinh(T(NaN))) | |||
for x in Iterators.flatten(pcnfloat.(T == Float64 ? [2.0^-28,2.0,2.0^28] : [2f0^-12,2f0,22f28])) | |||
for x in Iterators.flatten(pcnfloat.([T(2)^-28,T(2),T(2)])) |
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.
The second T(2)
doesn't look right. I assume you mean T(2)^28
?
test/math.jl
Outdated
@@ -919,7 +913,7 @@ end | |||
@test_throws DomainError acosh(T(0.1)) | |||
@test acosh(one(T)) === zero(T) | |||
@test isnan_type(T, acosh(T(NaN))) | |||
for x in (nextfloat(T(1)), pcnfloat(T(2))..., pcnfloat(T(2^28))...) | |||
for x in Iterators.flatten(pcnfloat.([T(1) T(2), T(2^28)])) |
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.
Shouldn't it be Iterators.flatten([nextfloat(T(1)); pcnfloat.([T(2), T(2^28)])])
? Or [nextfloat(T(1)), pcnfloat(T(2))..., pcnfloat(T(2^28))...]
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.
I seem to have pushed without this fix.
Thanks for great comments @stevengj ! The tests are so much cleaner now. |
Still need to fix the license stuff and add benchmarks in BaseBenchmarks.
Edit: since I added some inverse hyperbolics as well, their tests are missing.