Skip to content

Commit c748963

Browse files
lucascolleyizaid
authored andcommitted
MAINT: xsf: add exp, log funcs
Co-authored-by: Irwin Zaid <[email protected]>
1 parent 30b15ce commit c748963

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

include/xsf/exp.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include "xsf/cephes/exp10.h"
4+
#include "xsf/cephes/exp2.h"
5+
6+
namespace xsf {
7+
8+
inline double expm1(double x) { return cephes::expm1(x); }
9+
10+
inline float expm1(float x) { return expm1(static_cast<double>(x)); }
11+
12+
// cexpm1(z) = cexp(z) - 1
13+
//
14+
// The imaginary part of this is easily computed via exp(z.real)*sin(z.imag)
15+
// The real part is difficult to compute when there is cancellation e.g. when
16+
// z.real = -log(cos(z.imag)). There isn't a way around this problem that
17+
// doesn't involve computing exp(z.real) and/or cos(z.imag) to higher
18+
// precision.
19+
inline std::complex<double> expm1(std::complex<double> z) {
20+
if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) {
21+
return std::exp(z) - 1.0;
22+
}
23+
24+
double x;
25+
double ezr = 0;
26+
if (std::real(z) <= -40) {
27+
x = -1.0;
28+
} else {
29+
ezr = expm1(std::real(z));
30+
x = ezr * std::cos(std::imag(z)) + cosm1(std::imag(z));
31+
}
32+
33+
// don't compute exp(zr) too, unless necessary
34+
double y;
35+
if (std::real(z) > -1.0) {
36+
y = (ezr + 1.0) * sin(std::imag(z));
37+
} else {
38+
y = exp(std::real(z)) * sin(std::imag(z));
39+
}
40+
41+
return std::complex<double>{x, y};
42+
}
43+
44+
inline std::complex<float> expm1(std::complex<float> z) {
45+
return static_cast<std::complex<float>>(expm1(static_cast<std::complex<double>>(z)));
46+
}
47+
48+
double exp2(double x) { return cephes::exp2(x); }
49+
50+
float exp2(float x) { return exp2(static_cast<double>(x)); }
51+
52+
double exp10(double x) { return cephes::exp10(x); }
53+
54+
float exp10(float x) { return exp10(static_cast<double>(x)); }
55+
56+
} // namespace xsf

include/xsf/log.h

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#pragma once
2+
3+
#include "cephes/dd_real.h"
4+
#include "trig.h"
5+
6+
namespace xsf {
7+
8+
inline double log1p(double x) { return cephes::log1p(x); }
9+
10+
inline float log1p(float x) { return log1p(static_cast<double>(x)); }
11+
12+
inline std::complex<double> clog1p_ddouble(double zr, double zi) {
13+
double x, y;
14+
15+
cephes::detail::double_double r(zr);
16+
cephes::detail::double_double i(zi);
17+
cephes::detail::double_double two(2.0);
18+
19+
cephes::detail::double_double rsqr = r * r;
20+
cephes::detail::double_double isqr = i * i;
21+
cephes::detail::double_double rtwo = two * r;
22+
cephes::detail::double_double absm1 = rsqr + isqr;
23+
absm1 = absm1 + rtwo;
24+
25+
x = 0.5 * log1p(static_cast<double>(absm1));
26+
y = atan2(zi, zr + 1.0);
27+
return std::complex<double>{x, y};
28+
}
29+
30+
// log(z + 1) = log(x + 1 + 1j*y)
31+
// = log(sqrt((x+1)**2 + y**2)) + 1j*atan2(y, x+1)
32+
//
33+
// Using atan2(y, x+1) for the imaginary part is always okay. The real part
34+
// needs to be calculated more carefully. For |z| large, the naive formula
35+
// log(z + 1) can be used. When |z| is small, rewrite as
36+
//
37+
// log(sqrt((x+1)**2 + y**2)) = 0.5*log(x**2 + 2*x +1 + y**2)
38+
// = 0.5 * log1p(x**2 + y**2 + 2*x)
39+
// = 0.5 * log1p(hypot(x,y) * (hypot(x, y) + 2*x/hypot(x,y)))
40+
//
41+
// This expression suffers from cancellation when x < 0 and
42+
// y = +/-sqrt(2*fabs(x)). To get around this cancellation problem, we use
43+
// double-double precision when necessary.
44+
inline std::complex<double> log1p(std::complex<double> z) {
45+
double x, y, az, azi;
46+
47+
if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) {
48+
z = z + 1.0;
49+
return std::log(z);
50+
}
51+
52+
double zr = z.real();
53+
double zi = z.imag();
54+
55+
if (zi == 0.0 && zr >= -1.0) {
56+
return log1p(zr);
57+
}
58+
59+
az = std::abs(z);
60+
if (az < 0.707) {
61+
azi = std::fabs(zi);
62+
if (zr < 0 && std::abs(-zr - azi * azi / 2) / (-zr) < 0.5) {
63+
return clog1p_ddouble(zr, zi);
64+
} else {
65+
x = 0.5 * log1p(az * (az + 2 * zr / az));
66+
y = atan2(zi, zr + 1.0);
67+
return std::complex<double>(x, y);
68+
}
69+
}
70+
71+
z = z + 1.0;
72+
return std::log(z);
73+
}
74+
75+
inline std::complex<float> log1p(std::complex<float> z) {
76+
return static_cast<std::complex<float>>(log1p(static_cast<std::complex<double>>(z)));
77+
}
78+
79+
inline double log1pmx(double x) { return cephes::log1pmx(x); }
80+
81+
inline float log1pmx(float x) { return log1pmx(static_cast<double>(x)); }
82+
83+
template <typename T>
84+
T xlogy(T x, T y) {
85+
if (x == 0 && !std::isnan(y)) {
86+
return 0;
87+
}
88+
89+
return x * std::log(y);
90+
}
91+
92+
template <typename T>
93+
std::complex<T> xlogy(std::complex<T> x, std::complex<T> y) {
94+
if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) {
95+
return 0;
96+
}
97+
98+
return x * std::log(y);
99+
}
100+
101+
template <typename T>
102+
T xlog1py(T x, T y) {
103+
if (x == 0 && !std::isnan(y)) {
104+
return 0;
105+
}
106+
107+
return x * log1p(y);
108+
}
109+
110+
template <typename T>
111+
std::complex<T> xlog1py(std::complex<T> x, std::complex<T> y) {
112+
if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) {
113+
return 0;
114+
}
115+
116+
return x * log1p(y);
117+
}
118+
119+
} // namespace xsf

0 commit comments

Comments
 (0)