Skip to content

Commit 1683729

Browse files
committed
Added inverse trig and hyperbolics, some rough test coverage.
1 parent 0c066bd commit 1683729

File tree

5 files changed

+318
-18
lines changed

5 files changed

+318
-18
lines changed

Sources/ComplexModule/ElementaryFunctions.swift

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@
2828
// project.
2929
// 6. Give the best performance we can. We should care about performance,
3030
// but with lower precedence than the other considerations.
31+
//
32+
// Except where derivations are given, the expressions used here are all
33+
// adapted from Kahan's 1986 paper "Branch Cuts for Complex Elementary
34+
// Functions; or: Much Ado About Nothing's Sign Bit".
3135

3236
import RealModule
3337

3438
// TODO: uncomment conformance once all implementations are provided.
35-
extension Complex /*: ElementaryFunctions */ {
39+
extension Complex: ElementaryFunctions {
3640

3741
// MARK: - exp-like functions
3842

@@ -47,7 +51,6 @@ extension Complex /*: ElementaryFunctions */ {
4751
/// Note that naive evaluation of this expression in floating-point would be prone to premature
4852
/// overflow, since `cos` and `sin` both have magnitude less than 1 for most inputs (i.e.
4953
/// `exp(x)` may be infinity when `exp(x) cos(y)` would not be.
50-
@inlinable
5154
public static func exp(_ z: Complex) -> Complex {
5255
guard z.isFinite else { return z }
5356
// If x < log(greatestFiniteMagnitude), then exp(x) does not overflow.
@@ -62,7 +65,6 @@ extension Complex /*: ElementaryFunctions */ {
6265
return Complex(.cos(z.y), .sin(z.y)).multiplied(by: .exp(z.x))
6366
}
6467

65-
@inlinable
6668
public static func expMinusOne(_ z: Complex) -> Complex {
6769
// exp(x + iy) - 1 = (exp(x) cos(y) - 1) + i exp(x) sin(y)
6870
// -------- u --------
@@ -132,7 +134,6 @@ extension Complex /*: ElementaryFunctions */ {
132134
// This function and sinh should stay in sync; if you make a
133135
// modification here, you should almost surely make a parallel
134136
// modification to sinh below.
135-
@inlinable @inline(__always)
136137
public static func cosh(_ z: Complex) -> Complex {
137138
guard z.isFinite else { return z }
138139
guard z.x.magnitude < -RealType.log(.ulpOfOne) else {
@@ -162,7 +163,6 @@ extension Complex /*: ElementaryFunctions */ {
162163
// sinh(x + iy) = sinh(x) cos(y) + i cosh(x) sinh(y)
163164
//
164165
// See cosh above for algorithm details.
165-
@inlinable @inline(__always)
166166
public static func sinh(_ z: Complex) -> Complex {
167167
guard z.isFinite else { return z }
168168
guard z.x.magnitude < -RealType.log(.ulpOfOne) else {
@@ -178,7 +178,6 @@ extension Complex /*: ElementaryFunctions */ {
178178
}
179179

180180
// tanh(z) = sinh(z) / cosh(z)
181-
@inlinable
182181
public static func tanh(_ z: Complex) -> Complex {
183182
guard z.isFinite else { return z }
184183
// Note that when |x| is larger than -log(.ulpOfOne),
@@ -219,7 +218,6 @@ extension Complex /*: ElementaryFunctions */ {
219218
}
220219

221220
// MARK: - log-like functions
222-
@inlinable
223221
public static func log(_ z: Complex) -> Complex {
224222
// If z is zero or infinite, the phase is undefined, so the result is
225223
// the single exceptional value.
@@ -319,7 +317,6 @@ extension Complex /*: ElementaryFunctions */ {
319317
return Complex(.log(onePlus: s)/2, θ)
320318
}
321319

322-
@inlinable
323320
public static func log(onePlus z: Complex) -> Complex {
324321
// If either |x| or |y| is bounded away from the origin, we don't need
325322
// any extra precision, and can just literally compute log(1+z). Note
@@ -330,7 +327,7 @@ extension Complex /*: ElementaryFunctions */ {
330327
guard 2*z.x.magnitude < 1 && z.y.magnitude < 1 else { return log(1+z) }
331328
// z is in (±0.5, ±1), so we need to evaluate more carefully.
332329
// The imaginary part is straightforward:
333-
let θ = z.phase
330+
let θ = (1+z).phase
334331
// For the real part, we _could_ use the same approach that we do for
335332
// log( ), but we'd need an extra-precise (1+x)², which can potentially
336333
// be quite painful to calculate. Instead, we can use an approach that
@@ -352,13 +349,17 @@ extension Complex /*: ElementaryFunctions */ {
352349
}
353350

354351
public static func acos(_ z: Complex) -> Complex {
355-
fatalError()
352+
Complex(
353+
2*RealType.atan2(y: sqrt(1-z).real, x: sqrt(1+z).real),
354+
RealType.asinh((sqrt(1+z).conjugate * sqrt(1-z)).imaginary)
355+
)
356356
}
357357

358-
// asin(z) = -i*asinh(iz)
359358
public static func asin(_ z: Complex) -> Complex {
360-
let w = asinh(Complex(-z.y, z.x))
361-
return Complex(w.y, -w.x)
359+
Complex(
360+
RealType.atan2(y: z.x, x: (sqrt(1-z) * sqrt(1+z)).real),
361+
RealType.asinh((sqrt(1-z).conjugate * sqrt(1+z)).imaginary)
362+
)
362363
}
363364

364365
// atan(z) = -i*atanh(iz)
@@ -368,15 +369,29 @@ extension Complex /*: ElementaryFunctions */ {
368369
}
369370

370371
public static func acosh(_ z: Complex) -> Complex {
371-
fatalError()
372+
Complex(
373+
RealType.asinh((sqrt(z-1).conjugate * sqrt(z+1)).real),
374+
2*RealType.atan2(y: sqrt(z-1).imaginary, x: sqrt(z+1).real)
375+
)
372376
}
373377

378+
// asinh(z) = -i*asin(iz)
374379
public static func asinh(_ z: Complex) -> Complex {
375-
fatalError()
380+
let w = asin(Complex(-z.y, z.x))
381+
return Complex(w.y, -w.x)
376382
}
377383

378384
public static func atanh(_ z: Complex) -> Complex {
379-
fatalError()
385+
// TODO: Kahan uses a much more complicated expression here; possibly
386+
// simply because he didn't have a complex log(1+z) with good
387+
// characteristics. Investigate tradeoffs further.
388+
//
389+
// Further TODO: decide policy for point at infinity / NaN. Unlike most
390+
// of these functions, atanh doesn't have a pole at infinity; convention
391+
// in C-family languages is use one value in the upper half plane, and
392+
// another in the lower. Requires some thought about the most appropriate
393+
// way to handle this case in Swift.
394+
(log(onePlus: z) - log(onePlus:-z))/2
380395
}
381396

382397
// MARK: - pow-like functions

Sources/_NumericsShims/include/_NumericsShims.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,10 @@ HEADER_SHIM CComplex libm_cmul(CComplex z, CComplex w) {
417417
double _Complex c = a*b;
418418
return (CComplex){ __real__ c, __imag__ c };
419419
}
420+
421+
HEADER_SHIM CComplex libm_catanh(CComplex z) {
422+
double _Complex a = { z.real, z.imag };
423+
double _Complex w = __builtin_catanh(a);
424+
return (CComplex){ __real__ w, __imag__ w };
425+
}
420426
#endif // !defined _WIN32

0 commit comments

Comments
 (0)