Skip to content

Commit 2cbb4ae

Browse files
authored
Merge pull request #413 from andrewjradcliffe/ajr/purejulia_lgamma
Pure Julia implementation of OpenLibm's `lgamma`, `lgamma_r`
2 parents 2b077a4 + 83071be commit 2cbb4ae

File tree

6 files changed

+444
-10
lines changed

6 files changed

+444
-10
lines changed

src/SpecialFunctions.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ include("erf.jl")
8585
include("ellip.jl")
8686
include("expint.jl")
8787
include("sincosint.jl")
88+
include("logabsgamma/e_lgammaf_r.jl")
89+
include("logabsgamma/e_lgamma_r.jl")
8890
include("gamma.jl")
8991
include("gamma_inc.jl")
9092
include("betanc.jl")

src/gamma.jl

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -598,16 +598,6 @@ See also [`loggamma`](@ref).
598598
"""
599599
logabsgamma(x::Real) = _logabsgamma(float(x))
600600

601-
function _logabsgamma(x::Float64)
602-
signp = Ref{Int32}()
603-
y = ccall((:lgamma_r,libopenlibm), Float64, (Float64, Ptr{Int32}), x, signp)
604-
return y, Int(signp[])
605-
end
606-
function _logabsgamma(x::Float32)
607-
signp = Ref{Int32}()
608-
y = ccall((:lgammaf_r,libopenlibm), Float32, (Float32, Ptr{Int32}), x, signp)
609-
return y, Int(signp[])
610-
end
611601
function _logabsgamma(x::Float16)
612602
y, s = _logabsgamma(Float32(x))
613603
return Float16(y), s
@@ -649,6 +639,7 @@ function _loggamma(x::Real)
649639
return y
650640
end
651641

642+
652643
function _loggamma(x::BigFloat)
653644
isnan(x) && return x
654645
y = BigFloat()

src/logabsgamma/e_lgamma_r.jl

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#=
2+
/* @(#)e_lgamma_r.c 1.3 95/01/18 */
3+
/*
4+
* ====================================================
5+
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
6+
*
7+
* Developed at SunSoft, a Sun Microsystems, Inc. business.
8+
* Permission to use, copy, modify, and distribute this
9+
* software is freely granted, provided that this notice
10+
* is preserved.
11+
* ====================================================
12+
*
13+
*/
14+
=#
15+
16+
#=
17+
18+
/* __ieee754_lgamma_r(x, signgamp)
19+
* Reentrant version of the logarithm of the Gamma function
20+
* with user provide pointer for the sign of Gamma(x).
21+
*
22+
* Method:
23+
* 1. Argument Reduction for 0 < x <= 8
24+
* Since gamma(1+s)=s*gamma(s), for x in [0,8], we may
25+
* reduce x to a number in [1.5,2.5] by
26+
* lgamma(1+s) = log(s) + lgamma(s)
27+
* for example,
28+
* lgamma(7.3) = log(6.3) + lgamma(6.3)
29+
* = log(6.3*5.3) + lgamma(5.3)
30+
* = log(6.3*5.3*4.3*3.3*2.3) + lgamma(2.3)
31+
* 2. Polynomial approximation of lgamma around its
32+
* minimun ymin=1.461632144968362245 to maintain monotonicity.
33+
* On [ymin-0.23, ymin+0.27] (i.e., [1.23164,1.73163]), use
34+
* Let z = x-ymin;
35+
* lgamma(x) = -1.214862905358496078218 + z^2*poly(z)
36+
* where
37+
* poly(z) is a 14 degree polynomial.
38+
* 2. Rational approximation in the primary interval [2,3]
39+
* We use the following approximation:
40+
* s = x-2.0;
41+
* lgamma(x) = 0.5*s + s*P(s)/Q(s)
42+
* with accuracy
43+
* |P/Q - (lgamma(x)-0.5s)| < 2**-61.71
44+
* Our algorithms are based on the following observation
45+
*
46+
* zeta(2)-1 2 zeta(3)-1 3
47+
* lgamma(2+s) = s*(1-Euler) + --------- * s - --------- * s + ...
48+
* 2 3
49+
*
50+
* where Euler = 0.5771... is the Euler constant, which is very
51+
* close to 0.5.
52+
*
53+
* 3. For x>=8, we have
54+
* lgamma(x)~(x-0.5)log(x)-x+0.5*log(2pi)+1/(12x)-1/(360x**3)+....
55+
* (better formula:
56+
* lgamma(x)~(x-0.5)*(log(x)-1)-.5*(log(2pi)-1) + ...)
57+
* Let z = 1/x, then we approximation
58+
* f(z) = lgamma(x) - (x-0.5)(log(x)-1)
59+
* by
60+
* 3 5 11
61+
* w = w0 + w1*z + w2*z + w3*z + ... + w6*z
62+
* where
63+
* |w - f(z)| < 2**-58.74
64+
*
65+
* 4. For negative x, since (G is gamma function)
66+
* -x*G(-x)*G(x) = pi/sin(pi*x),
67+
* we have
68+
* G(x) = pi/(sin(pi*x)*(-x)*G(-x))
69+
* since G(-x) is positive, sign(G(x)) = sign(sin(pi*x)) for x<0
70+
* Hence, for x<0, signgam = sign(sin(pi*x)) and
71+
* lgamma(x) = log(|Gamma(x)|)
72+
* = log(pi/(|x*sin(pi*x)|)) - lgamma(-x);
73+
* Note: one should avoid compute pi*(-x) directly in the
74+
* computation of sin(pi*(-x)).
75+
*
76+
* 5. Special Cases
77+
* lgamma(2+s) ~ s*(1-Euler) for tiny s
78+
* lgamma(1) = lgamma(2) = 0
79+
* lgamma(x) ~ -log(|x|) for tiny x
80+
* lgamma(0) = lgamma(neg.integer) = inf and raise divide-by-zero
81+
* lgamma(inf) = inf
82+
* lgamma(-inf) = inf (bug for bug compatible with C99!?)
83+
*
84+
*/
85+
86+
=#
87+
88+
# Matches OpenLibm behavior (except commented out |x|≥2^52 early exit)
89+
function _logabsgamma(x::Float64)
90+
ux = reinterpret(UInt64, x)
91+
hx = ux >>> 32 % Int32
92+
lx = ux % UInt32
93+
94+
#= purge off +-inf, NaN, +-0, tiny and negative arguments =#
95+
signgam = 1
96+
isneg = hx < Int32(0)
97+
ix = hx & 0x7fffffff
98+
ix 0x7ff00000 && return x * x, signgam
99+
ix | lx == 0x00000000 && return Inf, signgam
100+
if ix < 0x3b900000 #= |x|<2**-70, return -log(|x|) =#
101+
if isneg
102+
signgam = -1
103+
return -log(-x), signgam
104+
else
105+
return -log(x), signgam
106+
end
107+
end
108+
if isneg
109+
# ix ≥ 0x43300000 && return Inf, signgam #= |x|>=2**52, must be -integer =#
110+
t = sinpi(x)
111+
iszero(t) && return Inf, signgam #= -integer =#
112+
nadj = logπ - log(abs(t * x))
113+
if t < 0.0; signgam = -1; end
114+
x = -x
115+
end
116+
if ix < 0x40000000 #= x < 2.0 =#
117+
i = round(x, RoundToZero)
118+
f = x - i
119+
if f == 0.0 #= purge off 1; 2 handled by x < 8.0 branch =#
120+
return 0.0, signgam
121+
elseif i == 1.0
122+
r = 0.0
123+
c = 1.0
124+
else
125+
r = -log(x)
126+
c = 0.0
127+
end
128+
if f 0.7315998077392578
129+
y = 1.0 + c - x
130+
z = y * y
131+
p1 = @evalpoly(z, 7.72156649015328655494e-02, 6.73523010531292681824e-02, 7.38555086081402883957e-03, 1.19270763183362067845e-03, 2.20862790713908385557e-04, 2.52144565451257326939e-05)
132+
p2 = z * @evalpoly(z, 3.22467033424113591611e-01, 2.05808084325167332806e-02, 2.89051383673415629091e-03, 5.10069792153511336608e-04, 1.08011567247583939954e-04, 4.48640949618915160150e-05)
133+
p = muladd(p1, y, p2)
134+
r += muladd(y, -0.5, p)
135+
elseif f 0.2316399812698364 # or, the lb? 0.2316322326660156
136+
y = x - 0.46163214496836225 - c
137+
z = y * y
138+
w = z * y
139+
p1 = @evalpoly(w, 4.83836122723810047042e-01, -3.27885410759859649565e-02, 6.10053870246291332635e-03, -1.40346469989232843813e-03, 3.15632070903625950361e-04)
140+
p2 = @evalpoly(w, -1.47587722994593911752e-01, 1.79706750811820387126e-02, -3.68452016781138256760e-03, 8.81081882437654011382e-04, -3.12754168375120860518e-04)
141+
p3 = @evalpoly(w, 6.46249402391333854778e-02, -1.03142241298341437450e-02, 2.25964780900612472250e-03, -5.38595305356740546715e-04, 3.35529192635519073543e-04)
142+
p = muladd(z, p1, -muladd(w, -muladd(p3, y, p2), -3.63867699703950536541e-18))
143+
r += p - 1.21486290535849611461e-1
144+
else
145+
y = x - c
146+
p1 = y * @evalpoly(y, -7.72156649015328655494e-02, 6.32827064025093366517e-01, 1.45492250137234768737, 9.77717527963372745603e-01, 2.28963728064692451092e-01, 1.33810918536787660377e-02)
147+
p2 = @evalpoly(y, 1.0, 2.45597793713041134822, 2.12848976379893395361, 7.69285150456672783825e-01, 1.04222645593369134254e-01, 3.21709242282423911810e-03)
148+
r += muladd(y, -0.5, p1 / p2)
149+
end
150+
elseif ix < 0x40200000 #= x < 8.0 =#
151+
i = round(x, RoundToZero)
152+
y = x - i
153+
z = 1.0
154+
p = 0.0
155+
u = x
156+
while u 3.0
157+
p -= 1.0
158+
u = x + p
159+
z *= u
160+
end
161+
p = y * @evalpoly(y, -7.72156649015328655494e-2, 2.14982415960608852501e-1, 3.25778796408930981787e-1, 1.46350472652464452805e-1, 2.66422703033638609560e-2, 1.84028451407337715652e-3, 3.19475326584100867617e-5)
162+
q = @evalpoly(y, 1.0, 1.39200533467621045958, 7.21935547567138069525e-1, 1.71933865632803078993e-1, 1.86459191715652901344e-2, 7.77942496381893596434e-4, 7.32668430744625636189e-6)
163+
r = log(z) + muladd(0.5, y, p / q)
164+
elseif ix < 0x43900000 #= 8.0 ≤ x < 2^58 =#
165+
z = 1.0 / x
166+
y = z * z
167+
w = muladd(z, @evalpoly(y, 8.33333333333329678849e-2, -2.77777777728775536470e-3, 7.93650558643019558500e-4, -5.95187557450339963135e-4, 8.36339918996282139126e-4, -1.63092934096575273989e-3), 4.18938533204672725052e-1)
168+
r = muladd(x - 0.5, log(x) - 1.0, w)
169+
else #= 2^58 ≤ x ≤ Inf =#
170+
r = muladd(x, log(x), -x)
171+
end
172+
if isneg
173+
r = nadj - r
174+
end
175+
return r, signgam
176+
end

src/logabsgamma/e_lgammaf_r.jl

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#=
2+
/* e_lgammaf_r.c -- float version of e_lgamma_r.c.
3+
* Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com.
4+
*/
5+
6+
/*
7+
* ====================================================
8+
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
9+
*
10+
* Developed at SunPro, a Sun Microsystems, Inc. business.
11+
* Permission to use, copy, modify, and distribute this
12+
* software is freely granted, provided that this notice
13+
* is preserved.
14+
* ====================================================
15+
*/
16+
=#
17+
18+
# Matches OpenLibm behavior exactly, including return of sign
19+
function _logabsgamma(x::Float32)
20+
hx = reinterpret(Int32, x)
21+
22+
#= purge off +-inf, NaN, +-0, tiny and negative arguments =#
23+
signgam = 1
24+
isneg = hx < Int32(0)
25+
ix = hx & 0x7fffffff
26+
ix 0x7f800000 && return x * x, signgam
27+
ix == 0x00000000 && return Inf32, signgam
28+
if ix < 0x35000000 #= |x|<2**-21, return -log(|x|) =#
29+
if isneg
30+
signgam = -1
31+
return -log(-x), signgam
32+
else
33+
return -log(x), signgam
34+
end
35+
end
36+
if isneg
37+
# ix ≥ 0x4b000000 && return Inf32, signgam #= |x|>=2**23, must be -integer =#
38+
t = sinpi(x)
39+
t == 0.0f0 && return Inf32, signgam #= -integer =#
40+
nadj = logπ - log(abs(t * x))
41+
if t < 0.0f0; signgam = -1; end
42+
x = -x
43+
end
44+
45+
if ix < 0x40000000 #= x < 2.0 =#
46+
i = round(x, RoundToZero)
47+
f = x - i
48+
if f == 0.0f0 #= purge off 1; 2 handled by x < 8.0 branch =#
49+
return 0.0f0, signgam
50+
elseif i == 1.0f0
51+
r = 0.0f0
52+
c = 1.0f0
53+
else
54+
r = -log(x)
55+
c = 0.0f0
56+
end
57+
if f 0.7315998f0
58+
y = 1.0f0 + c - x
59+
z = y * y
60+
p1 = @evalpoly(z, 7.7215664089f-2, 6.7352302372f-2, 7.3855509982f-3, 1.1927076848f-3, 2.2086278477f-4, 2.5214456400f-5)
61+
p2 = z * @evalpoly(z, 3.2246702909f-1, 2.0580807701f-2, 2.8905137442f-3, 5.1006977446f-4, 1.0801156895f-4, 4.4864096708f-5)
62+
p = muladd(p1, y, p2)
63+
r += muladd(y, -0.5f0, p)
64+
elseif f 0.23163998f0 # or, the lb? 0.2316322f0
65+
y = x - 0.46163213f0 - c
66+
z = y * y
67+
w = z * y
68+
p1 = @evalpoly(w, 4.8383611441f-1, -3.2788541168f-2, 6.1005386524f-3, -1.4034647029f-3, 3.1563205994f-4)
69+
p2 = @evalpoly(w, -1.4758771658f-1, 1.7970675603f-2, -3.6845202558f-3, 8.8108185446f-4, -3.1275415677f-4)
70+
p3 = @evalpoly(w, 6.4624942839f-2, -1.0314224288f-2, 2.2596477065f-3, -5.3859531181f-4, 3.3552918467f-4)
71+
p = muladd(z, p1, -muladd(w, -muladd(p3, y, p2), 6.6971006518f-9))
72+
r += p - 1.2148628384f-1
73+
else
74+
y = x - c
75+
p1 = y * @evalpoly(y, -7.7215664089f-2, 6.3282704353f-1, 1.4549225569f0, 9.7771751881f-1, 2.2896373272f-1, 1.3381091878f-2)
76+
p2 = @evalpoly(y, 1.0f0, 2.4559779167f0, 2.1284897327f0, 7.6928514242f-1, 1.0422264785f-1, 3.2170924824f-3)
77+
r += muladd(y, -0.5f0, p1 / p2)
78+
end
79+
elseif ix < 0x41000000 #= x < 8.0 =#
80+
i = round(x, RoundToZero)
81+
y = x - i
82+
z = 1.0f0
83+
p = 0.0f0
84+
u = x
85+
while u 3.0f0
86+
p -= 1.0f0
87+
u = x + p
88+
z *= u
89+
end
90+
p = y * @evalpoly(y, -7.7215664089f-2, 2.1498242021f-1, 3.2577878237f-1, 1.4635047317f-1, 2.6642270386f-2, 1.8402845599f-3, 3.1947532989f-5)
91+
q = @evalpoly(y, 1.0f0, 1.3920053244f0, 7.2193557024f-1, 1.7193385959f-1, 1.8645919859f-2, 7.7794247773f-4, 7.3266842264f-6)
92+
r = log(z) + muladd(0.5f0, y, p / q)
93+
elseif ix < 0x5c800000 #= 8.0 ≤ x < 2^58 =#
94+
z = 1.0f0 / x
95+
y = z * z
96+
w = muladd(z, @evalpoly(y, 8.3333335817f-2, -2.7777778450f-3, 7.9365057172f-4, -5.9518753551f-4, 8.3633989561f-4, -1.6309292987f-3), 4.1893854737f-1)
97+
r = muladd(x - 0.5f0, log(x) - 1.0f0, w)
98+
else
99+
#= 2^58 ≤ x ≤ Inf =#
100+
r = muladd(x, log(x), -x)
101+
end
102+
if isneg
103+
r = nadj - r
104+
end
105+
return r, signgam
106+
end

0 commit comments

Comments
 (0)