float_backend API

The float_backend package provides the analytic functions of Complex[Double]: modulus and argument, division, roots, exponentials and logarithms, powers, and the trigonometric and hyperbolic functions with their inverses. It also defines three capability traits for floating-point scalars, implemented for Float and Double. The derivations of the formulas and the branch cuts are in the float_backend design.

Source: src/float_backend/ (float_backend_traits.mbt, complex_elementary.mbt, complex_trigonometric.mbt, complex_hyperbolic.mbt).

Importing

import {
  "Luna-Flow/luna-complex/float_backend" @fb,
}

The examples use @fb for this package and @complex for the root package. All functions are free functions over Complex[Double] (MoonBit does not let a package add methods to a type of another package).

Conventions

  • Principal values. Multi-valued functions return one value of a fixed branch, described per function. arg returns values in (−π,π](-\pi, \pi] except on the negative real axis (see the warning below).
  • Special values. NaN and infinities are handled where stated (div, atan, atanh, acosh, abs_log, pow); elsewhere they propagate through ordinary Double arithmetic and may produce NaN parts.
  • Reciprocal functions abort at poles. sec, csc, cot, sech, csch, coth, asec, acsc, asech, acsch, acoth and the exponent −1-1 of pow use Complex::inv, which aborts with Double::inv: division by zero on a zero modulus.

Capability traits

FloatingAnalyticScalar

The algebraic and analytic capabilities an analytic backend needs from its real scalar.

pub(open) trait FloatingAnalyticScalar : @luna-generic.Field + @luna-generic.Num + Compare + @arithmetic.Constants + @arithmetic.Sqrt + @arithmetic.Exponential + @arithmetic.Logarithmic + @arithmetic.Trigonometric + @arithmetic.InverseTrigonometric + @arithmetic.Hyperbolic + @arithmetic.InverseHyperbolic {
}
pub impl FloatingAnalyticScalar for Float
pub impl FloatingAnalyticScalar for Double

It has no methods of its own: it names the composition of luna-generic and arithmetic traits.

FloatingSpecialValues

IEEE 754 special values and their tests.

pub(open) trait FloatingSpecialValues {
  fn nan() -> Self
  fn infinity() -> Self
  fn neg_infinity() -> Self
  fn is_nan(Self) -> Bool
  fn is_inf(Self) -> Bool
  fn is_pos_inf(Self) -> Bool
  fn is_neg_inf(Self) -> Bool
  fn is_negative_zero(Self) -> Bool
}
pub impl FloatingSpecialValues for Float
pub impl FloatingSpecialValues for Double

is_negative_zero(x) is is_neg_inf(1.0 / x), so it is true exactly for −0-0.

FloatingBackendScalar

The primitives the numerically stable algorithms use, on top of the two traits above.

pub(open) trait FloatingBackendScalar : FloatingAnalyticScalar + FloatingSpecialValues {
  fn from_double(Double) -> Self
  fn trunc(Self) -> Self
  fn to_int(Self) -> Int
  fn hypot(Self, Self) -> Self
  fn log1p(Self) -> Self
}
pub impl FloatingBackendScalar for Float
pub impl FloatingBackendScalar for Double
MethodMeaningDoubleFloat
from_doubleconvert a Double constantidentityFloat::from_double
truncround towards zeroDouble::truncFloat::trunc
to_intconvert to IntDouble::to_intFloat::to_int
hypotx2+y2\sqrt{x^2 + y^2} without overflow@math.hypot@math.hypotf
log1pln⁡(1+x)\ln(1 + x)@math.log1pln(1.0 + x)

The Float log1p is the direct formula, which loses relative accuracy for ∣x∣≪1|x| \ll 1. No public function of this package is generic over these traits yet; the Complex[Double] functions below call the Double primitives directly.

fn[T : @fb.FloatingSpecialValues] classify(x : T) -> String {
  if @fb.FloatingSpecialValues::is_nan(x) {
    "nan"
  } else if @fb.FloatingSpecialValues::is_inf(x) {
    "inf"
  } else if @fb.FloatingSpecialValues::is_negative_zero(x) {
    "-0"
  } else {
    "finite"
  }
}

test "special values" {
  assert_eq(classify(-0.0), "-0")
  assert_eq(classify(@double.infinity), "inf")
  let nan : Float = @fb.FloatingSpecialValues::nan()
  assert_eq(classify(nan), "nan")
  assert_eq(@fb.FloatingBackendScalar::hypot(3.0, 4.0), 5.0)
}

Re-exported type

Complex

@fb.Complex is @complex.Complex; see the core API.

pub using @luna-complex {type Complex}

Construction and storage

polar

Builds reiθ=rcos⁡θ+i rsin⁡θr e^{i\theta} = r\cos\theta + i\,r\sin\theta.

pub fn polar(Double, Double) -> Complex[Double]

No normalization is applied: a negative rr or any real θ\theta is accepted.

pack

Writes zz into an interleaved buffer [re0, im0, re1, im1, …] at complex index offset.

pub fn pack(Complex[Double], Array[Double], Int) -> Unit

native_pack

Writes a real and an imaginary part into an interleaved buffer.

pub fn native_pack(Double, Double, Array[Double], Int) -> Unit

The buffer length must be even. If offset equals the number of complex entries (arr.length() / 2), the pair is appended; otherwise it overwrites entry offset. An odd length aborts with native_pack: buffer length must be even, an offset outside 0..=arr.length() / 2 with native_pack: offset out of bounds.

test "polar and packing" {
  let z = @fb.polar(2.0, 0.0)
  assert_eq(z, @complex.Complex::new(2.0, 0.0))
  let buf : Array[Double] = []
  @fb.pack(z, buf, 0)
  @fb.native_pack(3.0, 4.0, buf, 1)
  @fb.native_pack(5.0, 6.0, buf, 0)
  assert_eq(buf, [5.0, 6.0, 3.0, 4.0])
}

Modulus and argument

abs

Returns ∣z∣=x2+y2|z| = \sqrt{x^2 + y^2} with hypot, without intermediate overflow or underflow.

pub fn abs(Complex[Double]) -> Double

abs_sqr

Returns ∣z∣2|z|^2, computed as m2((x/m)2+(y/m)2)m^2\big((x/m)^2 + (y/m)^2\big) with m=max⁡(∣x∣,∣y∣)m = \max(|x|, |y|).

pub fn abs_sqr(Complex[Double]) -> Double

The scaling avoids spurious underflow of the squares; the result itself overflows to infinity when ∣z∣2|z|^2 exceeds the Double range. 00 maps to 00.

abs_log

Returns ln⁡∣z∣\ln|z| as ln⁡m+12ln⁡(1+t2)\ln m + \tfrac12\ln(1 + t^2) with m=max⁡(∣x∣,∣y∣)m = \max(|x|,|y|) and t=min⁡/max⁡t = \min/\max.

pub fn abs_log(Complex[Double]) -> Double

It is finite for every finite non-zero zz, even when ∣z∣|z| itself would overflow. abs_log(0) is −∞-\infty.

arg

Returns the argument θ\theta with z=∣z∣eiθz = |z| e^{i\theta}.

pub fn arg(Complex[Double]) -> Double
InputResult
z=0z = 0 (either zero sign)00
y=±0y = \pm 0, x<0x < 02π2\pi (see the warning above)
otherwiseatan2(y, x) ∈(−π,π)\in (-\pi, \pi)
test "modulus and argument" {
  let z = @complex.Complex::new(3.0, 4.0)
  assert_eq(@fb.abs(z), 5.0)
  assert_eq(@fb.abs_sqr(z), 25.0)
  assert_eq(@fb.arg(@complex.Complex::new(0.0, 1.0)), @math.PI / 2.0)
  let huge = @complex.Complex::new(1.0e300, 1.0e300)
  assert_true(@fb.abs_sqr(huge).is_inf())
  assert_true(@fb.abs_log(huge) < 692.0) // ln(sqrt 2 * 1e300) is about 691.1
}

Division

div

Divides z/wz / w with Smith’s scaling and IEEE special-value handling.

pub fn div(Complex[Double], Complex[Double]) -> Complex[Double]

For finite ww it divides by the larger of ∣c∣,∣d∣|c|, |d| first (with r=d/cr = d/c when ∣c∣≥∣d∣|c| \ge |d|):

a+bic+di=(a+br)+(b−ar) ic (1+r2),\frac{a + bi}{c + di} = \frac{(a + b r) + (b - a r)\,i}{c\,(1 + r^2)} ,

so no square of cc or dd is formed. When zz has no NaN part and ww has an infinite part, the result is computed from the signs of the infinities: a finite zz gives ±0\pm 0 parts, an infinite zz gives the quotient of the sign patterns. Division by 0+0i0 + 0i produces infinities or NaN (no abort).

test "robust division" {
  let one = @complex.Complex::new(1.0, 0.0)
  let tiny = @complex.Complex::new(1.0e-300, 1.0e-300)
  let q = @fb.div(one, tiny)
  assert_true(q.re > 4.9e299 && q.im < -4.9e299)
  let z = @fb.div(@complex.Complex::new(1.0, 2.0), @complex.Complex::new(@double.infinity, 0.0))
  assert_eq(z.re, 0.0)
}

Roots

sqrt

Returns the principal square root: Re⁡z≥0\operatorname{Re}\sqrt z \ge 0, with the branch cut along the negative real axis.

pub fn sqrt(Complex[Double]) -> Complex[Double]

It computes w=(∣x∣+∣z∣)/2w = \sqrt{(|x| + |z|)/2} in a scaled form and returns w+y2wiw + \frac{y}{2w} i for x≥0x \ge 0, and ∣y∣2w±w i\frac{|y|}{2w} \pm w\,i for x<0x < 0 with the sign of yy. On the cut (y=±0y = \pm 0, x<0x < 0) the result is +∣x∣ i+\sqrt{|x|}\,i for both zero signs. sqrt(0) is 00.

sqrt_real

Square root of a real number as a complex number: x\sqrt x for x≥0x \ge 0, i−xi\sqrt{-x} for x<0x < 0.

pub fn sqrt_real(Double) -> Complex[Double]
test "square roots" {
  assert_eq(@fb.sqrt(@complex.Complex::new(-3.0, 4.0)), @complex.Complex::new(1.0, 2.0))
  assert_eq(@fb.sqrt(@complex.Complex::new(-4.0, 0.0)), @complex.Complex::new(0.0, 2.0))
  assert_eq(@fb.sqrt_real(-9.0), @complex.Complex::new(0.0, 3.0))
}

Exponential and logarithms

exp

Returns ez=ex(cos⁡y+isin⁡y)e^z = e^x(\cos y + i\sin y).

pub fn exp(Complex[Double]) -> Complex[Double]

exe^x is computed first, so it overflows for x>709.78x > 709.78 even when a part of the result would be finite, and exp(710 + 0i) has a NaN imaginary part (∞⋅0\infty \cdot 0).

log

Returns ln⁡∣z∣+iarg⁡z\ln|z| + i\arg z with abs_log and arg.

pub fn log(Complex[Double]) -> Complex[Double]

The imaginary part follows arg, so it is 2π2\pi on the negative real axis. log(0) is −∞+0i-\infty + 0i.

log_10

Returns log⁡10z=log⁡z/ln⁡10\log_{10} z = \log z / \ln 10.

pub fn log_10(Complex[Double]) -> Complex[Double]

log_b

Returns log⁡bz=log⁡z/log⁡b\log_b z = \log z / \log b, divided with div.

pub fn log_b(Complex[Double], Complex[Double]) -> Complex[Double]
test "exponential and logarithm" {
  let z = @complex.Complex::new(1.0, 2.0)
  let back = @fb.exp(@fb.log(z))
  assert_true((back.re - 1.0).abs() < 1.0e-12 && (back.im - 2.0).abs() < 1.0e-12)
  let l = @fb.log_10(@complex.Complex::new(100.0, 0.0))
  assert_true((l.re - 2.0).abs() < 1.0e-15)
}

Powers

pow

Returns zwz^w.

pub fn pow(Complex[Double], Complex[Double]) -> Complex[Double]

The cases are tried in order:

CaseResult
z=0z = 0, w=0w = 011
z=0z = 0, ww real and positive00
z=0z = 0, otherwiseNaN + NaNii
w=1w = 1zz
w=−1w = -1z.inv() (aborts if z=0z = 0, which the first rows exclude)
ww real integer, ∣w∣≤231−1\lvert w\rvert \le 2^{31} - 1binary powering; negative exponents invert zz first
otherwiseewlog⁡ze^{w\log z} in polar form

The polar form computes ρ=eRe⁡wln⁡∣z∣−Im⁡warg⁡z\rho = e^{\operatorname{Re} w \ln|z| - \operatorname{Im} w \arg z} and β=Re⁡warg⁡z+Im⁡wln⁡∣z∣\beta = \operatorname{Re} w \arg z + \operatorname{Im} w \ln|z| and returns ρ(cos⁡β+isin⁡β)\rho(\cos\beta + i\sin\beta).

pow_real

Returns zpz^p for a real exponent pp, with the same zero, integer and polar cases as pow.

pub fn pow_real(Complex[Double], Double) -> Complex[Double]
test "powers" {
  let z = @complex.Complex::new(1.0, 2.0)
  assert_eq(@fb.pow_real(z, 2.0), @complex.Complex::new(-3.0, 4.0))
  let r = @fb.pow(@complex.Complex::new(0.0, 1.0), @complex.Complex::new(0.5, 0.0))
  assert_true((r.re - 0.7071067811865476).abs() < 1.0e-15)
  assert_true(@fb.pow_real(@complex.Complex::new(0.0, 0.0), -1.0).re.is_nan())
}

Scalar operands

op_bin_re

Applies a binary complex function with a real second operand x+0ix + 0i.

pub fn op_bin_re(Complex[Double], Double, (Complex[Double], Complex[Double]) -> Complex[Double]) -> Complex[Double]

op_bin_im

Applies a binary complex function with an imaginary second operand 0+yi0 + yi.

pub fn op_bin_im(Complex[Double], Double, (Complex[Double], Complex[Double]) -> Complex[Double]) -> Complex[Double]
test "scalar operands" {
  let z = @complex.Complex::new(1.0, 2.0)
  assert_eq(@fb.op_bin_re(z, 2.0, @fb.div), @complex.Complex::new(0.5, 1.0))
  assert_eq(@fb.op_bin_im(z, 1.0, (a, b) => a * b), @complex.Complex::new(-2.0, 1.0))
}

Trigonometric functions

sin

Returns sin⁡z=sin⁡xcosh⁡y+icos⁡xsinh⁡y\sin z = \sin x\cosh y + i\cos x\sinh y; for y=0y = 0 exactly, the real sin⁡x\sin x.

pub fn sin(Complex[Double]) -> Complex[Double]

cos

Returns cos⁡z=cos⁡xcosh⁡y−isin⁡xsinh⁡y\cos z = \cos x\cosh y - i\sin x\sinh y; for y=0y = 0 exactly, the real cos⁡x\cos x.

pub fn cos(Complex[Double]) -> Complex[Double]

tan

Returns tan⁡z\tan z.

pub fn tan(Complex[Double]) -> Complex[Double]

For ∣y∣<1|y| < 1 it uses sin⁡2x+isinh⁡2y2(cos⁡2x+sinh⁡2y)\dfrac{\sin 2x + i\sinh 2y}{2(\cos^2 x + \sinh^2 y)}; for ∣y∣≥1|y| \ge 1 a form scaled by e−2∣y∣e^{-2|y|} that tends to ±i\pm i without overflow (see the design).

sec

Returns 1/cos⁡z1/\cos z; aborts where cos⁡z=0\cos z = 0.

pub fn sec(Complex[Double]) -> Complex[Double]

csc

Returns 1/sin⁡z1/\sin z; aborts at z=kπz = k\pi.

pub fn csc(Complex[Double]) -> Complex[Double]

cot

Returns 1/tan⁡z1/\tan z; aborts at z=kπz = k\pi.

pub fn cot(Complex[Double]) -> Complex[Double]
test "trigonometric functions" {
  let z = @complex.Complex::new(1.0, 1.0)
  let s = @fb.sin(z)
  let c = @fb.cos(z)
  let one = s * s + c * c
  assert_true((one.re - 1.0).abs() < 1.0e-15 && one.im.abs() < 1.0e-15)
  let t = @fb.tan(@complex.Complex::new(1.0, 100.0))
  assert_eq(t.im, 1.0)
}

Inverse trigonometric functions

asin

Returns the principal arcsine, with branch cuts on the real axis outside [−1,1][-1, 1]; Re⁡\operatorname{Re} lies in [−π/2,π/2][-\pi/2, \pi/2].

pub fn asin(Complex[Double]) -> Complex[Double]

Real inputs go to asin_real, purely imaginary inputs to iasinh⁡yi\operatorname{asinh} y, inputs with a part above 1015010^{150} to the asymptotic form atan2⁡(∣x∣,∣y∣)+i(ln⁡2+ln⁡∣z∣)\operatorname{atan2}(|x|, |y|) + i(\ln 2 + \ln|z|), and all others to the Hull–Fairgrieve–Tang algorithm (see the design). The result is odd in each part: the signs of xx and yy are copied to the real and imaginary parts.

asin_real

Arcsine of a real number: arcsin⁡x\arcsin x for ∣x∣≤1|x| \le 1, ±π/2+iacosh⁡∣x∣\pm\pi/2 + i\operatorname{acosh}|x| for ∣x∣>1|x| > 1 (sign of xx), NaN for NaN.

pub fn asin_real(Double) -> Complex[Double]

acos

Returns the arccosine, with branch cuts on the real axis outside [−1,1][-1, 1].

pub fn acos(Complex[Double]) -> Complex[Double]

For x≥0x \ge 0 the real part is the principal value in [0,π/2][0, \pi/2] and the imaginary part has the sign opposite to yy. For x<0x < 0 the imaginary part is the principal one, but the real part is 2π−ρ2\pi - \rho instead of the principal π−ρ\pi - \rho, where ρ∈[0,π/2]\rho \in [0, \pi/2] is the real part of arccos⁡(−z)\arccos(-z) (see the warning above). Purely imaginary inputs give π/2−iasinh⁡y\pi/2 - i\operatorname{asinh} y, and real inputs go to acos_real.

acos_real

Arccosine of a real number: arccos⁡x\arccos x for ∣x∣≤1|x| \le 1, −iacosh⁡x-i\operatorname{acosh} x for x>1x > 1, and 2π−iacosh⁡(−x)2\pi - i\operatorname{acosh}(-x) for x<−1x < -1.

pub fn acos_real(Double) -> Complex[Double]

atan

Returns the principal arctangent, with branch cuts on the imaginary axis outside [−i,i][-i, i].

pub fn atan(Complex[Double]) -> Complex[Double]

The imaginary part is 14ln⁡x2+(1+y)2x2+(1−y)2\tfrac14\ln\frac{x^2 + (1+y)^2}{x^2 + (1-y)^2}, computed with log1p when the ratio is close to 11; the real part is 12atan2⁡(2x,1−x2−y2)\tfrac12\operatorname{atan2}(2x, 1 - x^2 - y^2), rescaled for large inputs. On the cut (x=0x = 0, ∣y∣>1|y| > 1) the real part is ±π/2\pm\pi/2 with the sign of yy. Infinite inputs return ±π/2+0i\pm\pi/2 + 0i.

asec

Returns acos⁡(1/z)\operatorname{acos}(1/z); aborts at z=0z = 0.

pub fn asec(Complex[Double]) -> Complex[Double]

asec_real

Arcsecant of a real number: arccos⁡(1/x)\arccos(1/x) for ∣x∣≥1|x| \ge 1, −iacosh⁡(1/x)-i\operatorname{acosh}(1/x) for 0≤x<10 \le x < 1, 2π−iacosh⁡(−1/x)2\pi - i\operatorname{acosh}(-1/x) for −1<x<0-1 < x < 0.

pub fn asec_real(Double) -> Complex[Double]

acsc

Returns asin⁡(1/z)\operatorname{asin}(1/z); aborts at z=0z = 0.

pub fn acsc(Complex[Double]) -> Complex[Double]

acsc_real

Arccosecant of a real number: arcsin⁡(1/x)\arcsin(1/x) for ∣x∣≥1|x| \ge 1, ±π/2+iacosh⁡∣1/x∣\pm\pi/2 + i\operatorname{acosh}|1/x| for ∣x∣<1|x| < 1.

pub fn acsc_real(Double) -> Complex[Double]

acot

Returns atan⁡(1/z)\operatorname{atan}(1/z), and π/2\pi/2 at z=0z = 0.

pub fn acot(Complex[Double]) -> Complex[Double]
test "inverse trigonometric functions" {
  let z = @complex.Complex::new(1.0, 1.0)
  let back = @fb.sin(@fb.asin(z))
  assert_true((back.re - 1.0).abs() < 1.0e-10 && (back.im - 1.0).abs() < 1.0e-10)
  assert_eq(@fb.asin_real(2.0).re, @math.PI / 2.0)
  assert_eq(@fb.acot(@complex.Complex::new(0.0, 0.0)).re, @math.PI / 2.0)
}

Hyperbolic functions

sinh

Returns sinh⁡z=sinh⁡xcos⁡y+icosh⁡xsin⁡y\sinh z = \sinh x\cos y + i\cosh x\sin y.

pub fn sinh(Complex[Double]) -> Complex[Double]

cosh

Returns cosh⁡z=cosh⁡xcos⁡y+isinh⁡xsin⁡y\cosh z = \cosh x\cos y + i\sinh x\sin y.

pub fn cosh(Complex[Double]) -> Complex[Double]

tanh

Returns tanh⁡z\tanh z; for ∣x∣≥1|x| \ge 1 a form scaled by e−2∣x∣e^{-2|x|} that tends to ±1\pm 1 without overflow.

pub fn tanh(Complex[Double]) -> Complex[Double]

sech

Returns 1/cosh⁡z1/\cosh z; aborts where cosh⁡z=0\cosh z = 0.

pub fn sech(Complex[Double]) -> Complex[Double]

csch

Returns 1/sinh⁡z1/\sinh z; aborts at z=kπiz = k\pi i.

pub fn csch(Complex[Double]) -> Complex[Double]

coth

Returns 1/tanh⁡z1/\tanh z; aborts at z=kπiz = k\pi i.

pub fn coth(Complex[Double]) -> Complex[Double]

Inverse hyperbolic functions

asinh

Returns asinh⁡z=−iasin⁡(iz)\operatorname{asinh} z = -i\operatorname{asin}(iz), with branch cuts on the imaginary axis outside [−i,i][-i, i].

pub fn asinh(Complex[Double]) -> Complex[Double]

Real inputs use the real asinh; purely imaginary inputs iyiy give iarcsin⁡yi\arcsin y for ∣y∣≤1|y| \le 1 and ±(acosh⁡∣y∣+iπ/2)\pm(\operatorname{acosh}|y| + i\pi/2) otherwise.

acosh

Returns acosh⁡z=2ln⁡(z+12+z−12)\operatorname{acosh} z = 2\ln\Big(\sqrt{\tfrac{z+1}{2}} + \sqrt{\tfrac{z-1}{2}}\Big), with the branch cut on the real axis below 11.

pub fn acosh(Complex[Double]) -> Complex[Double]

Real inputs go to acosh_real; an infinite part gives ∞+iarg⁡z\infty + i\arg z.

acosh_real

Inverse hyperbolic cosine of a real number: acosh⁡x\operatorname{acosh} x for x≥1x \ge 1, iarccos⁡xi\arccos x for −1≤x<1-1 \le x < 1, acosh⁡(−x)+2πi\operatorname{acosh}(-x) + 2\pi i for x<−1x < -1.

pub fn acosh_real(Double) -> Complex[Double]

atanh

Returns atanh⁡z\operatorname{atanh} z, with branch cuts on the real axis outside [−1,1][-1, 1].

pub fn atanh(Complex[Double]) -> Complex[Double]

The real part is 14ln⁡(1+x)2+y2(1−x)2+y2\tfrac14\ln\frac{(1+x)^2 + y^2}{(1-x)^2 + y^2} (with log1p near zero), the imaginary part 12atan2⁡(2y,1−x2−y2)\tfrac12\operatorname{atan2}(2y, 1 - x^2 - y^2), rescaled for large inputs. Infinite inputs return 0±iπ/20 \pm i\pi/2.

atanh_real

Inverse hyperbolic tangent of a real number: atanh⁡x\operatorname{atanh} x for ∣x∣<1|x| < 1, ±∞\pm\infty at ±1\pm 1, and atanh⁡(1/x)+iπ/2\operatorname{atanh}(1/x) + i\pi/2 for ∣x∣>1|x| > 1.

pub fn atanh_real(Double) -> Complex[Double]

asech

Returns acosh⁡(1/z)\operatorname{acosh}(1/z); aborts at z=0z = 0.

pub fn asech(Complex[Double]) -> Complex[Double]

acsch

Returns asinh⁡(1/z)\operatorname{asinh}(1/z); aborts at z=0z = 0.

pub fn acsch(Complex[Double]) -> Complex[Double]

acoth

Returns atanh⁡(1/z)\operatorname{atanh}(1/z); aborts at z=0z = 0.

pub fn acoth(Complex[Double]) -> Complex[Double]
test "hyperbolic functions" {
  let z = @complex.Complex::new(0.5, 0.25)
  let back = @fb.tanh(@fb.atanh(z))
  assert_true((back.re - 0.5).abs() < 1.0e-12 && (back.im - 0.25).abs() < 1.0e-12)
  let w = @fb.sinh(@fb.asinh(@complex.Complex::new(0.0, 2.0)))
  assert_true((w.im - 2.0).abs() < 1.0e-12)
  assert_eq(@fb.atanh_real(2.0).im, @math.PI / 2.0)
}