Skip to content

Commit c67ab28

Browse files
authored
more accurate and faster lgamma, and use a more standard branch cut (#18330)
* more accurate and faster lgamma, and use a more standard branch cut for real(x)<0 * more tests * use trick from Hare (1997) to compute log(prod of shifts) rather than sum(logs of shifts) with the correct branch cut * even more tests * whoops, use 1e14 and not 10^14 to avoid integer overflow on 32-bit Windows * fix accuracy near zero at z=2 * update manual for lgamma * news for lgamma changes * can use a lower-degree Taylor series around z=2 because the coefficients decrease faster * linewrap poly coefs
1 parent 5f74d16 commit c67ab28

File tree

4 files changed

+135
-36
lines changed

4 files changed

+135
-36
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ This section lists changes that do not have deprecation warnings.
2424
* Keyword arguments are processed left-to-right: if the same keyword is specified more than
2525
once, the rightmost occurrence takes precedence ([#17785]).
2626

27+
* The `lgamma(z)` function now uses a different (more standard) branch cut
28+
for `real(z) < 0`, which differs from `log(gamma(z))` by multiples of 2π
29+
in the imaginary part ([#18330]).
30+
2731
Library improvements
2832
--------------------
2933

@@ -648,3 +652,4 @@ Language tooling improvements
648652
[#17546]: https://github.com/JuliaLang/julia/issues/17546
649653
[#17668]: https://github.com/JuliaLang/julia/issues/17668
650654
[#17785]: https://github.com/JuliaLang/julia/issues/17785
655+
[#18330]: https://github.com/JuliaLang/julia/issues/18330

base/special/gamma.jl

+91-34
Original file line numberDiff line numberDiff line change
@@ -31,49 +31,106 @@ Compute the logarithmic factorial of `x`
3131
"""
3232
lfact(x::Real) = (x<=1 ? zero(float(x)) : lgamma(x+one(x)))
3333

34-
const clg_coeff = [76.18009172947146,
35-
-86.50532032941677,
36-
24.01409824083091,
37-
-1.231739572450155,
38-
0.1208650973866179e-2,
39-
-0.5395239384953e-5]
40-
41-
function clgamma_lanczos(z)
42-
const sqrt2pi = 2.5066282746310005
43-
44-
y = x = z
45-
temp = x + 5.5
46-
zz = log(temp)
47-
zz = zz * (x+0.5)
48-
temp -= zz
49-
ser = complex(1.000000000190015, 0)
50-
for j=1:6
51-
y += 1.0
52-
zz = clg_coeff[j]/y
53-
ser += zz
54-
end
55-
zz = sqrt2pi*ser / x
56-
return log(zz) - temp
57-
end
58-
5934
"""
6035
lgamma(x)
6136
6237
Compute the logarithm of the absolute value of [`gamma`](:func:`gamma`) for
6338
[`Real`](:obj:`Real`) `x`, while for [`Complex`](:obj:`Complex`) `x` it computes the
64-
logarithm of `gamma(x)`.
39+
principal branch cut of the logarithm of `gamma(x)` (defined for negative `real(x)`
40+
by analytic continuation from positive `real(x)`).
6541
"""
66-
function lgamma(z::Complex)
67-
if real(z) <= 0.5
68-
a = clgamma_lanczos(1-z)
69-
b = log(sinpi(z))
70-
const logpi = 1.14472988584940017
71-
z = logpi - b - a
42+
function lgamma end
43+
44+
# asymptotic series for log(gamma(z)), valid for sufficiently large real(z) or |imag(z)|
45+
@inline function lgamma_asymptotic(z::Complex{Float64})
46+
zinv = inv(z)
47+
t = zinv*zinv
48+
# coefficients are bernoulli[2:n+1] .// (2*(1:n).*(2*(1:n) - 1))
49+
return (z-0.5)*log(z) - z + 9.1893853320467274178032927e-01 + # <-- log(2pi)/2
50+
zinv*@evalpoly(t, 8.3333333333333333333333368e-02,-2.7777777777777777777777776e-03,
51+
7.9365079365079365079365075e-04,-5.9523809523809523809523806e-04,
52+
8.4175084175084175084175104e-04,-1.9175269175269175269175262e-03,
53+
6.4102564102564102564102561e-03,-2.9550653594771241830065352e-02)
54+
end
55+
56+
# Compute the logΓ(z) function using a combination of the asymptotic series,
57+
# the Taylor series around z=1 and z=2, the reflection formula, and the shift formula.
58+
# Many details of these techniques are discussed in D. E. G. Hare,
59+
# "Computing the principal branch of log-Gamma," J. Algorithms 25, pp. 221-236 (1997),
60+
# and similar techniques are used (in a somewhat different way) by the
61+
# SciPy loggamma function. The key identities are also described
62+
# at http://functions.wolfram.com/GammaBetaErf/LogGamma/
63+
function lgamma(z::Complex{Float64})
64+
x = real(z)
65+
y = imag(z)
66+
yabs = abs(y)
67+
if !isfinite(x) || !isfinite(y) # Inf or NaN
68+
if isinf(x) && isfinite(y)
69+
return Complex(x, x > 0 ? (y == 0 ? y : copysign(Inf, y)) : copysign(Inf, -y))
70+
elseif isfinite(x) && isinf(y)
71+
return Complex(-Inf, y)
72+
else
73+
return Complex(NaN, NaN)
74+
end
75+
elseif x > 7 || yabs > 7 # use the Stirling asymptotic series for sufficiently large x or |y|
76+
return lgamma_asymptotic(z)
77+
elseif x < 0.1 # use reflection formula to transform to x > 0
78+
if x == 0 && y == 0 # return Inf with the correct imaginary part for z == 0
79+
return Complex(Inf, signbit(x) ? copysign(oftype(x, pi), -y) : -y)
80+
end
81+
# the 2pi * floor(...) stuff is to choose the correct branch cut for log(sinpi(z))
82+
return Complex(1.1447298858494001741434262, # log(pi)
83+
copysign(6.2831853071795864769252842, y) # 2pi
84+
* floor(0.5*x+0.25)) -
85+
log(sinpi(z)) - lgamma(1-z)
86+
elseif abs(x - 1) + yabs < 0.1
87+
# taylor series around zero at z=1
88+
# ... coefficients are [-eulergamma; [(-1)^k * zeta(k)/k for k in 2:15]]
89+
w = Complex(x - 1, y)
90+
return w * @evalpoly(w, -5.7721566490153286060651188e-01,8.2246703342411321823620794e-01,
91+
-4.0068563438653142846657956e-01,2.705808084277845478790009e-01,
92+
-2.0738555102867398526627303e-01,1.6955717699740818995241986e-01,
93+
-1.4404989676884611811997107e-01,1.2550966952474304242233559e-01,
94+
-1.1133426586956469049087244e-01,1.000994575127818085337147e-01,
95+
-9.0954017145829042232609344e-02,8.3353840546109004024886499e-02,
96+
-7.6932516411352191472827157e-02,7.1432946295361336059232779e-02,
97+
-6.6668705882420468032903454e-02)
98+
elseif abs(x - 2) + yabs < 0.1
99+
# taylor series around zero at z=2
100+
# ... coefficients are [1-eulergamma; [(-1)^k * (zeta(k)-1)/k for k in 2:12]]
101+
w = Complex(x - 2, y)
102+
return w * @evalpoly(w, 4.2278433509846713939348812e-01,3.2246703342411321823620794e-01,
103+
-6.7352301053198095133246196e-02,2.0580808427784547879000897e-02,
104+
-7.3855510286739852662729527e-03,2.8905103307415232857531201e-03,
105+
-1.1927539117032609771139825e-03,5.0966952474304242233558822e-04,
106+
-2.2315475845357937976132853e-04,9.9457512781808533714662972e-05,
107+
-4.4926236738133141700224489e-05,2.0507212775670691553131246e-05)
108+
end
109+
# use recurrence relation lgamma(z) = lgamma(z+1) - log(z) to shift to x > 7 for asymptotic series
110+
shiftprod = Complex(x,yabs)
111+
x += 1
112+
sb = false # == signbit(imag(shiftprod)) == signbit(yabs)
113+
# To use log(product of shifts) rather than sum(logs of shifts),
114+
# we need to keep track of the number of + to - sign flips in
115+
# imag(shiftprod), as described in Hare (1997), proposition 2.2.
116+
signflips = 0
117+
while x <= 7
118+
shiftprod *= Complex(x,yabs)
119+
sb′ = signbit(imag(shiftprod))
120+
signflips += sb′ & (sb′ != sb)
121+
sb = sb′
122+
x += 1
123+
end
124+
shift = log(shiftprod)
125+
if signbit(y) # if y is negative, conjugate the shift
126+
shift = Complex(real(shift), signflips*-6.2831853071795864769252842 - imag(shift))
72127
else
73-
z = clgamma_lanczos(z)
128+
shift = Complex(real(shift), imag(shift) + signflips*6.2831853071795864769252842)
74129
end
75-
complex(real(z), angle_restrict_symm(imag(z)))
130+
return lgamma_asymptotic(Complex(x,y)) - shift
76131
end
132+
lgamma{T<:Union{Integer,Rational}}(z::Complex{T}) = lgamma(float(z))
133+
lgamma{T<:Union{Float32,Float16}}(z::Complex{T}) = Complex{T}(lgamma(Complex{Float64}(z)))
77134

78135
gamma(z::Complex) = exp(lgamma(z))
79136

doc/stdlib/math.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,7 @@ Mathematical Functions
14851485

14861486
.. Docstring generated from Julia source
14871487
1488-
Compute the logarithm of the absolute value of :func:`gamma` for :obj:`Real` ``x``\ , while for :obj:`Complex` ``x`` it computes the logarithm of ``gamma(x)``\ .
1488+
Compute the logarithm of the absolute value of :func:`gamma` for :obj:`Real` ``x``\ , while for :obj:`Complex` ``x`` it computes the principal branch cut of the logarithm of ``gamma(x)`` (defined for negative ``real(x)`` by analytic continuation from positive ``real(x)``\ ).
14891489

14901490
.. function:: lfact(x)
14911491

test/math.jl

+38-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ for elty in (Float32, Float64)
554554
end
555555
@test lgamma(1.4+3.7im) -3.7094025330996841898 + 2.4568090502768651184im
556556
@test lgamma(1.4+3.7im) log(gamma(1.4+3.7im))
557-
@test lgamma(-4.2+0im) lgamma(-4.2)-pi*im
557+
@test lgamma(-4.2+0im) lgamma(-4.2)-5pi*im
558558
@test factorial(3.0) == gamma(4.0) == factorial(3)
559559
for x in (3.2, 2+1im, 3//2, 3.2+0.1im)
560560
@test factorial(x) == gamma(1+x)
@@ -654,6 +654,43 @@ for x in -10.2:0.3456:50
654654
@test 1e-12 > relerr(digamma(x+0im), digamma(x))
655655
end
656656

657+
# lgamma test cases (from Wolfram Alpha)
658+
@test lgamma(-300im) -473.17185074259241355733179182866544204963885920016823743 - 1410.3490664555822107569308046418321236643870840962522425im
659+
@test lgamma(3.099) lgamma(3.099+0im) 0.786413746900558058720665860178923603134125854451168869796
660+
@test lgamma(1.15) lgamma(1.15+0im) -0.06930620867104688224241731415650307100375642207340564554
661+
@test lgamma(0.89) lgamma(0.89+0im) 0.074022173958081423702265889979810658434235008344573396963
662+
@test lgamma(0.91) lgamma(0.91+0im) 0.058922567623832379298241751183907077883592982094770449167
663+
@test lgamma(0.01) lgamma(0.01+0im) 4.599479878042021722513945411008748087261001413385289652419
664+
@test lgamma(-3.4-0.1im) -1.1733353322064779481049088558918957440847715003659143454 + 12.331465501247826842875586104415980094316268974671819281im
665+
@test lgamma(-13.4-0.1im) -22.457344044212827625152500315875095825738672314550695161 + 43.620560075982291551250251193743725687019009911713182478im
666+
@test lgamma(-13.4+0.0im) conj(lgamma(-13.4-0.0im)) -22.404285036964892794140985332811433245813398559439824988 - 43.982297150257105338477007365913040378760371591251481493im
667+
@test lgamma(-13.4+8im) -44.705388949497032519400131077242200763386790107166126534 - 22.208139404160647265446701539526205774669649081807864194im
668+
@test lgamma(1+exp2(-20)) lgamma(1+exp2(-20)+0im) -5.504750066148866790922434423491111098144565651836914e-7
669+
@test lgamma(1+exp2(-20)+exp2(-19)*im) -5.5047799872835333673947171235997541985495018556426e-7 - 1.1009485171695646421931605642091915847546979851020e-6im
670+
@test lgamma(-300+2im) -1419.3444991797240659656205813341478289311980525970715668 - 932.63768120761873747896802932133229201676713644684614785im
671+
@test lgamma(300+2im) 1409.19538972991765122115558155209493891138852121159064304 + 11.4042446282102624499071633666567192538600478241492492652im
672+
@test lgamma(1-6im) -7.6099596929506794519956058191621517065972094186427056304 - 5.5220531255147242228831899544009162055434670861483084103im
673+
@test lgamma(1-8im) -10.607711310314582247944321662794330955531402815576140186 - 9.4105083803116077524365029286332222345505790217656796587im
674+
@test lgamma(1+6.5im) conj(lgamma(1-6.5im)) -8.3553365025113595689887497963634069303427790125048113307 + 6.4392816159759833948112929018407660263228036491479825744im
675+
@test lgamma(1+1im) conj(lgamma(1-1im)) -0.6509231993018563388852168315039476650655087571397225919 - 0.3016403204675331978875316577968965406598997739437652369im
676+
@test lgamma(-pi*1e7 + 6im) -5.10911758892505772903279926621085326635236850347591e8 - 9.86959420047365966439199219724905597399295814979993e7im
677+
@test lgamma(-pi*1e7 + 8im) -5.10911765175690634449032797392631749405282045412624e8 - 9.86959074790854911974415722927761900209557190058925e7im
678+
@test lgamma(-pi*1e14 + 6im) -1.0172766411995621854526383224252727000270225301426e16 - 9.8696044010873714715264929863618267642124589569347e14im
679+
@test lgamma(-pi*1e14 + 8im) -1.0172766411995628137711690403794640541491261237341e16 - 9.8696044010867038531027376655349878694397362250037e14im
680+
@test lgamma(2.05 + 0.03im) conj(lgamma(2.05 - 0.03im)) 0.02165570938532611215664861849215838847758074239924127515 + 0.01363779084533034509857648574107935425251657080676603919im
681+
@test lgamma(2+exp2(-20)+exp2(-19)*im) 4.03197681916768997727833554471414212058404726357753e-7 + 8.06398296652953575754782349984315518297283664869951e-7im
682+
683+
# lgamma for non-finite arguments:
684+
@test lgamma(Inf + 0im) === Inf + 0im
685+
@test lgamma(Inf - 0.0im) === Inf - 0.0im
686+
@test lgamma(Inf + 1im) === Inf + Inf*im
687+
@test lgamma(Inf - 1im) === Inf - Inf*im
688+
@test lgamma(-Inf + 0.0im) === -Inf - Inf*im
689+
@test lgamma(-Inf - 0.0im) === -Inf + Inf*im
690+
@test lgamma(Inf*im) === -Inf + Inf*im
691+
@test lgamma(-Inf*im) === -Inf - Inf*im
692+
@test lgamma(Inf + Inf*im) === lgamma(NaN + 0im) === lgamma(NaN*im) === NaN + NaN*im
693+
657694
# digamma, trigamma, polygamma & zeta test cases (compared to Wolfram Alpha)
658695
@test digamma(7+0im) 1.872784335098467139393487909917597568957840664060076401194232
659696
@test digamma(7im) 1.94761433458434866917623737015561385331974500663251349960124 + 1.642224898223468048051567761191050945700191089100087841536im

0 commit comments

Comments
 (0)