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.
argreturns values in 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 ordinaryDoublearithmetic and may produce NaN parts. - Reciprocal functions abort at poles.
sec,csc,cot,sech,csch,coth,asec,acsc,asech,acsch,acothand the exponent ofpowuseComplex::inv, which aborts withDouble::inv: division by zeroon 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
.
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
| Method | Meaning | Double | Float |
|---|---|---|---|
from_double | convert a Double constant | identity | Float::from_double |
trunc | round towards zero | Double::trunc | Float::trunc |
to_int | convert to Int | Double::to_int | Float::to_int |
hypot | without overflow | @math.hypot | @math.hypotf |
log1p | @math.log1p | ln(1.0 + x) |
The Float log1p is the direct formula, which loses relative accuracy for
. 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 .
pub fn polar(Double, Double) -> Complex[Double]
No normalization is applied: a negative or any real is accepted.
pack
Writes 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 with hypot, without intermediate overflow
or underflow.
pub fn abs(Complex[Double]) -> Double
abs_sqr
Returns , computed as with .
pub fn abs_sqr(Complex[Double]) -> Double
The scaling avoids spurious underflow of the squares; the result itself
overflows to infinity when exceeds the Double range. maps to
.
abs_log
Returns as with and .
pub fn abs_log(Complex[Double]) -> Double
It is finite for every finite non-zero , even when itself would
overflow. abs_log(0) is .
arg
Returns the argument with .
pub fn arg(Complex[Double]) -> Double
| Input | Result |
|---|---|
| (either zero sign) | |
| , | (see the warning above) |
| otherwise | atan2(y, x) |
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 with Smith’s scaling and IEEE special-value handling.
pub fn div(Complex[Double], Complex[Double]) -> Complex[Double]
For finite it divides by the larger of first (with when ):
so no square of or is formed. When has no NaN part and has an infinite part, the result is computed from the signs of the infinities: a finite gives parts, an infinite gives the quotient of the sign patterns. Division by 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: , with the branch cut along the negative real axis.
pub fn sqrt(Complex[Double]) -> Complex[Double]
It computes in a scaled form and returns for , and for
with the sign of . On the cut (, ) the result is
for both zero signs. sqrt(0) is .
sqrt_real
Square root of a real number as a complex number: for , for .
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 .
pub fn exp(Complex[Double]) -> Complex[Double]
is computed first, so it overflows for even when a part
of the result would be finite, and exp(710 + 0i) has a NaN imaginary part
().
log
Returns with abs_log and arg.
pub fn log(Complex[Double]) -> Complex[Double]
The imaginary part follows arg, so it is on the negative real
axis. log(0) is .
log_10
Returns .
pub fn log_10(Complex[Double]) -> Complex[Double]
log_b
Returns , 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 .
pub fn pow(Complex[Double], Complex[Double]) -> Complex[Double]
The cases are tried in order:
| Case | Result |
|---|---|
| , | |
| , real and positive | |
| , otherwise | NaN + NaN |
z.inv() (aborts if , which the first rows exclude) | |
| real integer, | binary powering; negative exponents invert first |
| otherwise | in polar form |
The polar form computes and and returns .
pow_real
Returns for a real exponent , 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 .
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 .
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 ; for exactly, the real .
pub fn sin(Complex[Double]) -> Complex[Double]
cos
Returns ; for exactly, the real .
pub fn cos(Complex[Double]) -> Complex[Double]
tan
Returns .
pub fn tan(Complex[Double]) -> Complex[Double]
For it uses ; for a form scaled by that tends to without overflow (see the design).
sec
Returns ; aborts where .
pub fn sec(Complex[Double]) -> Complex[Double]
csc
Returns ; aborts at .
pub fn csc(Complex[Double]) -> Complex[Double]
cot
Returns ; aborts at .
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 ; lies in .
pub fn asin(Complex[Double]) -> Complex[Double]
Real inputs go to asin_real, purely imaginary inputs to , inputs with a part above to the asymptotic form
, and all others to the Hull–Fairgrieve–Tang algorithm (see the
design). The result is odd in each part: the signs of and are
copied to the real and imaginary parts.
asin_real
Arcsine of a real number: for , for (sign of ), NaN for NaN.
pub fn asin_real(Double) -> Complex[Double]
acos
Returns the arccosine, with branch cuts on the real axis outside .
pub fn acos(Complex[Double]) -> Complex[Double]
For the real part is the principal value in and the
imaginary part has the sign opposite to . For the imaginary part
is the principal one, but the real part is instead of the
principal , where is the real part of
(see the warning above). Purely imaginary inputs give , and real inputs go to acos_real.
acos_real
Arccosine of a real number: for , for , and for .
pub fn acos_real(Double) -> Complex[Double]
atan
Returns the principal arctangent, with branch cuts on the imaginary axis outside .
pub fn atan(Complex[Double]) -> Complex[Double]
The imaginary part is ,
computed with log1p when the ratio is close to ; the real part is
, rescaled for large
inputs. On the cut (, ) the real part is with
the sign of . Infinite inputs return .
asec
Returns ; aborts at .
pub fn asec(Complex[Double]) -> Complex[Double]
asec_real
Arcsecant of a real number: for , for , for .
pub fn asec_real(Double) -> Complex[Double]
acsc
Returns ; aborts at .
pub fn acsc(Complex[Double]) -> Complex[Double]
acsc_real
Arccosecant of a real number: for , for .
pub fn acsc_real(Double) -> Complex[Double]
acot
Returns , and at .
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 .
pub fn sinh(Complex[Double]) -> Complex[Double]
cosh
Returns .
pub fn cosh(Complex[Double]) -> Complex[Double]
tanh
Returns ; for a form scaled by that tends to without overflow.
pub fn tanh(Complex[Double]) -> Complex[Double]
sech
Returns ; aborts where .
pub fn sech(Complex[Double]) -> Complex[Double]
csch
Returns ; aborts at .
pub fn csch(Complex[Double]) -> Complex[Double]
coth
Returns ; aborts at .
pub fn coth(Complex[Double]) -> Complex[Double]
Inverse hyperbolic functions
asinh
Returns , with branch cuts on the imaginary axis outside .
pub fn asinh(Complex[Double]) -> Complex[Double]
Real inputs use the real asinh; purely imaginary inputs give for and otherwise.
acosh
Returns , with the branch cut on the real axis below .
pub fn acosh(Complex[Double]) -> Complex[Double]
Real inputs go to acosh_real; an infinite part gives .
acosh_real
Inverse hyperbolic cosine of a real number: for , for , for .
pub fn acosh_real(Double) -> Complex[Double]
atanh
Returns , with branch cuts on the real axis outside .
pub fn atanh(Complex[Double]) -> Complex[Double]
The real part is (with
log1p near zero), the imaginary part
, rescaled for large inputs. Infinite inputs return .
atanh_real
Inverse hyperbolic tangent of a real number: for , at , and for .
pub fn atanh_real(Double) -> Complex[Double]
asech
Returns ; aborts at .
pub fn asech(Complex[Double]) -> Complex[Double]
acsch
Returns ; aborts at .
pub fn acsch(Complex[Double]) -> Complex[Double]
acoth
Returns ; aborts at .
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)
}