Skip to content

Commit f3ee4e7

Browse files
authored
Merge pull request #92 from fredrikekre/fe/gamma
move [l]gamma, [l]beta and lfact from base
2 parents 5ff7cd3 + b98961c commit f3ee4e7

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

src/gamma.jl

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,228 @@ function polygamma(m::Integer, z::Number)
539539
# There is nothing to fallback to, since this didn't work
540540
polygamma(m, x)
541541
end
542+
543+
@static if VERSION >= v"0.7.0-alpha.69"
544+
545+
export gamma, lgamma, beta, lbeta, lfactorial
546+
547+
## from base/special/gamma.jl
548+
549+
gamma(x::Float64) = nan_dom_err(ccall((:tgamma,libm), Float64, (Float64,), x), x)
550+
gamma(x::Float32) = nan_dom_err(ccall((:tgammaf,libm), Float32, (Float32,), x), x)
551+
552+
"""
553+
gamma(x)
554+
555+
Compute the gamma function of `x`.
556+
"""
557+
gamma(x::Real) = gamma(float(x))
558+
559+
function lgamma_r(x::Float64)
560+
signp = Ref{Int32}()
561+
y = ccall((:lgamma_r,libm), Float64, (Float64, Ptr{Int32}), x, signp)
562+
return y, signp[]
563+
end
564+
function lgamma_r(x::Float32)
565+
signp = Ref{Int32}()
566+
y = ccall((:lgammaf_r,libm), Float32, (Float32, Ptr{Int32}), x, signp)
567+
return y, signp[]
568+
end
569+
lgamma_r(x::Real) = lgamma_r(float(x))
570+
lgamma_r(x::Number) = lgamma(x), 1 # lgamma does not take abs for non-real x
571+
"""
572+
lgamma_r(x)
573+
574+
Return L,s such that `gamma(x) = s * exp(L)`
575+
"""
576+
lgamma_r
577+
578+
"""
579+
lfactorial(x)
580+
581+
Compute the logarithmic factorial of a nonnegative integer `x`.
582+
Equivalent to [`lgamma`](@ref) of `x + 1`, but `lgamma` extends this function
583+
to non-integer `x`.
584+
"""
585+
lfactorial(x::Integer) = x < 0 ? throw(DomainError(x, "`x` must be non-negative.")) : lgamma(x + oneunit(x))
586+
Base.@deprecate lfact lfactorial
587+
588+
"""
589+
lgamma(x)
590+
591+
Compute the logarithm of the absolute value of [`gamma`](@ref) for
592+
[`Real`](@ref) `x`, while for [`Complex`](@ref) `x` compute the
593+
principal branch cut of the logarithm of `gamma(x)` (defined for negative `real(x)`
594+
by analytic continuation from positive `real(x)`).
595+
"""
596+
function lgamma end
597+
598+
# asymptotic series for log(gamma(z)), valid for sufficiently large real(z) or |imag(z)|
599+
@inline function lgamma_asymptotic(z::Complex{Float64})
600+
zinv = inv(z)
601+
t = zinv*zinv
602+
# coefficients are bernoulli[2:n+1] .// (2*(1:n).*(2*(1:n) - 1))
603+
return (z-0.5)*log(z) - z + 9.1893853320467274178032927e-01 + # <-- log(2pi)/2
604+
zinv*@evalpoly(t, 8.3333333333333333333333368e-02,-2.7777777777777777777777776e-03,
605+
7.9365079365079365079365075e-04,-5.9523809523809523809523806e-04,
606+
8.4175084175084175084175104e-04,-1.9175269175269175269175262e-03,
607+
6.4102564102564102564102561e-03,-2.9550653594771241830065352e-02)
608+
end
609+
610+
# Compute the logΓ(z) function using a combination of the asymptotic series,
611+
# the Taylor series around z=1 and z=2, the reflection formula, and the shift formula.
612+
# Many details of these techniques are discussed in D. E. G. Hare,
613+
# "Computing the principal branch of log-Gamma," J. Algorithms 25, pp. 221-236 (1997),
614+
# and similar techniques are used (in a somewhat different way) by the
615+
# SciPy loggamma function. The key identities are also described
616+
# at http://functions.wolfram.com/GammaBetaErf/LogGamma/
617+
function lgamma(z::Complex{Float64})
618+
x = real(z)
619+
y = imag(z)
620+
yabs = abs(y)
621+
if !isfinite(x) || !isfinite(y) # Inf or NaN
622+
if isinf(x) && isfinite(y)
623+
return Complex(x, x > 0 ? (y == 0 ? y : copysign(Inf, y)) : copysign(Inf, -y))
624+
elseif isfinite(x) && isinf(y)
625+
return Complex(-Inf, y)
626+
else
627+
return Complex(NaN, NaN)
628+
end
629+
elseif x > 7 || yabs > 7 # use the Stirling asymptotic series for sufficiently large x or |y|
630+
return lgamma_asymptotic(z)
631+
elseif x < 0.1 # use reflection formula to transform to x > 0
632+
if x == 0 && y == 0 # return Inf with the correct imaginary part for z == 0
633+
return Complex(Inf, signbit(x) ? copysign(oftype(x, pi), -y) : -y)
634+
end
635+
# the 2pi * floor(...) stuff is to choose the correct branch cut for log(sinpi(z))
636+
return Complex(1.1447298858494001741434262, # log(pi)
637+
copysign(6.2831853071795864769252842, y) # 2pi
638+
* floor(0.5*x+0.25)) -
639+
log(sinpi(z)) - lgamma(1-z)
640+
elseif abs(x - 1) + yabs < 0.1
641+
# taylor series around zero at z=1
642+
# ... coefficients are [-eulergamma; [(-1)^k * zeta(k)/k for k in 2:15]]
643+
w = Complex(x - 1, y)
644+
return w * @evalpoly(w, -5.7721566490153286060651188e-01,8.2246703342411321823620794e-01,
645+
-4.0068563438653142846657956e-01,2.705808084277845478790009e-01,
646+
-2.0738555102867398526627303e-01,1.6955717699740818995241986e-01,
647+
-1.4404989676884611811997107e-01,1.2550966952474304242233559e-01,
648+
-1.1133426586956469049087244e-01,1.000994575127818085337147e-01,
649+
-9.0954017145829042232609344e-02,8.3353840546109004024886499e-02,
650+
-7.6932516411352191472827157e-02,7.1432946295361336059232779e-02,
651+
-6.6668705882420468032903454e-02)
652+
elseif abs(x - 2) + yabs < 0.1
653+
# taylor series around zero at z=2
654+
# ... coefficients are [1-eulergamma; [(-1)^k * (zeta(k)-1)/k for k in 2:12]]
655+
w = Complex(x - 2, y)
656+
return w * @evalpoly(w, 4.2278433509846713939348812e-01,3.2246703342411321823620794e-01,
657+
-6.7352301053198095133246196e-02,2.0580808427784547879000897e-02,
658+
-7.3855510286739852662729527e-03,2.8905103307415232857531201e-03,
659+
-1.1927539117032609771139825e-03,5.0966952474304242233558822e-04,
660+
-2.2315475845357937976132853e-04,9.9457512781808533714662972e-05,
661+
-4.4926236738133141700224489e-05,2.0507212775670691553131246e-05)
662+
end
663+
# use recurrence relation lgamma(z) = lgamma(z+1) - log(z) to shift to x > 7 for asymptotic series
664+
shiftprod = Complex(x,yabs)
665+
x += 1
666+
sb = false # == signbit(imag(shiftprod)) == signbit(yabs)
667+
# To use log(product of shifts) rather than sum(logs of shifts),
668+
# we need to keep track of the number of + to - sign flips in
669+
# imag(shiftprod), as described in Hare (1997), proposition 2.2.
670+
signflips = 0
671+
while x <= 7
672+
shiftprod *= Complex(x,yabs)
673+
sb′ = signbit(imag(shiftprod))
674+
signflips += sb′ & (sb′ != sb)
675+
sb = sb′
676+
x += 1
677+
end
678+
shift = log(shiftprod)
679+
if signbit(y) # if y is negative, conjugate the shift
680+
shift = Complex(real(shift), signflips*-6.2831853071795864769252842 - imag(shift))
681+
else
682+
shift = Complex(real(shift), imag(shift) + signflips*6.2831853071795864769252842)
683+
end
684+
return lgamma_asymptotic(Complex(x,y)) - shift
685+
end
686+
lgamma(z::Complex{T}) where {T<:Union{Integer,Rational}} = lgamma(float(z))
687+
lgamma(z::Complex{T}) where {T<:Union{Float32,Float16}} = Complex{T}(lgamma(Complex{Float64}(z)))
688+
689+
gamma(z::Complex) = exp(lgamma(z))
690+
691+
"""
692+
beta(x, y)
693+
694+
Euler integral of the first kind ``\\operatorname{B}(x,y) = \\Gamma(x)\\Gamma(y)/\\Gamma(x+y)``.
695+
"""
696+
function beta(x::Number, w::Number)
697+
yx, sx = lgamma_r(x)
698+
yw, sw = lgamma_r(w)
699+
yxw, sxw = lgamma_r(x+w)
700+
return exp(yx + yw - yxw) * (sx*sw*sxw)
701+
end
702+
703+
"""
704+
lbeta(x, y)
705+
706+
Natural logarithm of the absolute value of the [`beta`](@ref)
707+
function ``\\log(|\\operatorname{B}(x,y)|)``.
708+
"""
709+
lbeta(x::Number, w::Number) = lgamma(x)+lgamma(w)-lgamma(x+w)
710+
711+
## from base/mpfr.jl
712+
713+
# Functions for which NaN results are converted to DomainError, following Base
714+
function gamma(x::BigFloat)
715+
isnan(x) && return x
716+
z = BigFloat()
717+
ccall((:mpfr_lgamma, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, ROUNDING_MODE[])
718+
isnan(z) && throw(DomainError(x, "NaN result for non-NaN input."))
719+
return z
720+
end
721+
722+
# log of absolute value of gamma function
723+
function lgamma_r(x::BigFloat)
724+
z = BigFloat()
725+
lgamma_signp = Ref{Cint}()
726+
ccall((:mpfr_lgamma,:libmpfr), Cint, (Ref{BigFloat}, Ref{Cint}, Ref{BigFloat}, Int32), z, lgamma_signp, x, ROUNDING_MODE[])
727+
return z, lgamma_signp[]
728+
end
729+
730+
lgamma(x::BigFloat) = lgamma_r(x)[1]
731+
732+
if Base.MPFR.version() >= v"4.0.0"
733+
function beta(y::BigFloat, x::BigFloat)
734+
z = BigFloat()
735+
ccall((:mpfr_beta, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Int32), z, y, x, ROUNDING_MODE[])
736+
return z
737+
end
738+
end
739+
740+
## from base/combinatorics.jl'
741+
742+
function gamma(n::Union{Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64})
743+
n < 0 && throw(DomainError(n, "`n` must not be negative."))
744+
n == 0 && return Inf
745+
n <= 2 && return 1.0
746+
n > 20 && return gamma(Float64(n))
747+
@inbounds return Float64(Base._fact_table64[n-1])
748+
end
749+
750+
## from base/math.jl
751+
752+
@inline lgamma(x::Float64) = nan_dom_err(ccall((:lgamma, libm), Float64, (Float64,), x), x)
753+
@inline lgamma(x::Float32) = nan_dom_err(ccall((:lgammaf, libm), Float32, (Float32,), x), x)
754+
@inline lgamma(x::Real) = lgamma(float(x))
755+
756+
## from base/numbers.jl
757+
# TODO: deprecate instead of doing this type-piracy here?
758+
Base.factorial(x::Number) = gamma(x + 1) # fallback for x not Integer
759+
760+
else # @static if
761+
762+
import Base: gamma, lgamma, beta, lbeta, lfact
763+
const lfactorial = lfact
764+
export lfactorial
765+
766+
end # @static if

test/runtests.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,3 +555,84 @@ end
555555
end
556556

557557
@test sprint(showerror, SF.AmosException(1)) == "AmosException with id 1: input error."
558+
559+
@testset "gamma and friends" begin
560+
@testset "gamma, lgamma (complex argument)" begin
561+
if Base.Math.libm == "libopenlibm"
562+
@test gamma.(Float64[1:25;]) == gamma.(1:25)
563+
else
564+
@test gamma.(Float64[1:25;]) gamma.(1:25)
565+
end
566+
for elty in (Float32, Float64)
567+
@test gamma(convert(elty,1/2)) convert(elty,sqrt(π))
568+
@test gamma(convert(elty,-1/2)) convert(elty,-2sqrt(π))
569+
@test lgamma(convert(elty,-1/2)) convert(elty,log(abs(gamma(-1/2))))
570+
end
571+
@test lgamma(1.4+3.7im) -3.7094025330996841898 + 2.4568090502768651184im
572+
@test lgamma(1.4+3.7im) log(gamma(1.4+3.7im))
573+
@test lgamma(-4.2+0im) lgamma(-4.2)-5pi*im
574+
@test factorial(3.0) == gamma(4.0) == factorial(3)
575+
for x in (3.2, 2+1im, 3//2, 3.2+0.1im)
576+
@test factorial(x) == gamma(1+x)
577+
end
578+
@test lfactorial(0) == lfactorial(1) == 0
579+
@test lfactorial(2) == lgamma(3)
580+
# Ensure that the domain of lfactorial matches that of factorial (issue #21318)
581+
@test_throws DomainError lfactorial(-3)
582+
@test_throws MethodError lfactorial(1.0)
583+
end
584+
585+
# lgamma test cases (from Wolfram Alpha)
586+
@test lgamma(-300im) -473.17185074259241355733179182866544204963885920016823743 - 1410.3490664555822107569308046418321236643870840962522425im
587+
@test lgamma(3.099) lgamma(3.099+0im) 0.786413746900558058720665860178923603134125854451168869796
588+
@test lgamma(1.15) lgamma(1.15+0im) -0.06930620867104688224241731415650307100375642207340564554
589+
@test lgamma(0.89) lgamma(0.89+0im) 0.074022173958081423702265889979810658434235008344573396963
590+
@test lgamma(0.91) lgamma(0.91+0im) 0.058922567623832379298241751183907077883592982094770449167
591+
@test lgamma(0.01) lgamma(0.01+0im) 4.599479878042021722513945411008748087261001413385289652419
592+
@test lgamma(-3.4-0.1im) -1.1733353322064779481049088558918957440847715003659143454 + 12.331465501247826842875586104415980094316268974671819281im
593+
@test lgamma(-13.4-0.1im) -22.457344044212827625152500315875095825738672314550695161 + 43.620560075982291551250251193743725687019009911713182478im
594+
@test lgamma(-13.4+0.0im) conj(lgamma(-13.4-0.0im)) -22.404285036964892794140985332811433245813398559439824988 - 43.982297150257105338477007365913040378760371591251481493im
595+
@test lgamma(-13.4+8im) -44.705388949497032519400131077242200763386790107166126534 - 22.208139404160647265446701539526205774669649081807864194im
596+
@test lgamma(1+exp2(-20)) lgamma(1+exp2(-20)+0im) -5.504750066148866790922434423491111098144565651836914e-7
597+
@test lgamma(1+exp2(-20)+exp2(-19)*im) -5.5047799872835333673947171235997541985495018556426e-7 - 1.1009485171695646421931605642091915847546979851020e-6im
598+
@test lgamma(-300+2im) -1419.3444991797240659656205813341478289311980525970715668 - 932.63768120761873747896802932133229201676713644684614785im
599+
@test lgamma(300+2im) 1409.19538972991765122115558155209493891138852121159064304 + 11.4042446282102624499071633666567192538600478241492492652im
600+
@test lgamma(1-6im) -7.6099596929506794519956058191621517065972094186427056304 - 5.5220531255147242228831899544009162055434670861483084103im
601+
@test lgamma(1-8im) -10.607711310314582247944321662794330955531402815576140186 - 9.4105083803116077524365029286332222345505790217656796587im
602+
@test lgamma(1+6.5im) conj(lgamma(1-6.5im)) -8.3553365025113595689887497963634069303427790125048113307 + 6.4392816159759833948112929018407660263228036491479825744im
603+
@test lgamma(1+1im) conj(lgamma(1-1im)) -0.6509231993018563388852168315039476650655087571397225919 - 0.3016403204675331978875316577968965406598997739437652369im
604+
@test lgamma(-pi*1e7 + 6im) -5.10911758892505772903279926621085326635236850347591e8 - 9.86959420047365966439199219724905597399295814979993e7im
605+
@test lgamma(-pi*1e7 + 8im) -5.10911765175690634449032797392631749405282045412624e8 - 9.86959074790854911974415722927761900209557190058925e7im
606+
@test lgamma(-pi*1e14 + 6im) -1.0172766411995621854526383224252727000270225301426e16 - 9.8696044010873714715264929863618267642124589569347e14im
607+
@test lgamma(-pi*1e14 + 8im) -1.0172766411995628137711690403794640541491261237341e16 - 9.8696044010867038531027376655349878694397362250037e14im
608+
@test lgamma(2.05 + 0.03im) conj(lgamma(2.05 - 0.03im)) 0.02165570938532611215664861849215838847758074239924127515 + 0.01363779084533034509857648574107935425251657080676603919im
609+
@test lgamma(2+exp2(-20)+exp2(-19)*im) 4.03197681916768997727833554471414212058404726357753e-7 + 8.06398296652953575754782349984315518297283664869951e-7im
610+
611+
@testset "lgamma for non-finite arguments" begin
612+
@test lgamma(Inf + 0im) === Inf + 0im
613+
@test lgamma(Inf - 0.0im) === Inf - 0.0im
614+
@test lgamma(Inf + 1im) === Inf + Inf*im
615+
@test lgamma(Inf - 1im) === Inf - Inf*im
616+
@test lgamma(-Inf + 0.0im) === -Inf - Inf*im
617+
@test lgamma(-Inf - 0.0im) === -Inf + Inf*im
618+
@test lgamma(Inf*im) === -Inf + Inf*im
619+
@test lgamma(-Inf*im) === -Inf - Inf*im
620+
@test lgamma(Inf + Inf*im) === lgamma(NaN + 0im) === lgamma(NaN*im) === NaN + NaN*im
621+
end
622+
end
623+
624+
@testset "beta, lbeta" begin
625+
@test beta(3/2,7/2) 5π/128
626+
@test beta(3,5) 1/105
627+
@test lbeta(5,4) log(beta(5,4))
628+
@test beta(5,4) beta(4,5)
629+
@test beta(-1/2, 3) beta(-1/2 + 0im, 3 + 0im) -16/3
630+
@test lbeta(-1/2, 3) log(16/3)
631+
@test beta(Float32(5),Float32(4)) == beta(Float32(4),Float32(5))
632+
@test beta(3,5) beta(3+0im,5+0im)
633+
@test(beta(3.2+0.1im,5.3+0.3im) exp(lbeta(3.2+0.1im,5.3+0.3im))
634+
0.00634645247782269506319336871208405439180447035257028310080 -
635+
0.00169495384841964531409376316336552555952269360134349446910im)
636+
637+
@test beta(big(1.0),big(1.2)) beta(1.0,1.2) rtol=4*eps()
638+
end

0 commit comments

Comments
 (0)