float_backend design
This page derives the formulas that float_backend implements for
Complex[Double], explains how each one avoids overflow, underflow and
cancellation, states the branch cuts and principal values, and explains why
the scalar capabilities are split into three traits.
Design goal
Provide the elementary functions of for Complex[Double] with
principal values on documented branch cuts, numerically stable formulas
over the whole Double range, and the IEEE special values handled where
it matters, while keeping all of this out of the generic
core.
Mathematical background
Polar form
Every has a polar form with . The angle is determined modulo ; the principal argument is the representative in , which equals off the negative real axis.
The principal logarithm and its branch cut
The solutions of are . The principal logarithm takes :
jumps by across the negative real axis, so is analytic on and is its branch cut. All other multi-valued functions are defined through and inherit cuts from it.11 W. Kahan, “Branch cuts for complex elementary functions, or much ado about nothing’s sign bit”, in The State of the Art in Numerical Analysis, Clarendon Press, 1987. It also discusses how a signed zero can select the side of a cut.
Principal values of the other functions
| Function | Branch cuts | Principal range |
|---|---|---|
log | ||
sqrt | ||
pow | in (non-integer ) | from |
asin | ||
acos | ||
atan | ||
asinh | ||
acosh | , | |
atanh |
The reciprocal functions are compositions: , , and so on.
Real and imaginary parts
The trigonometric and hyperbolic functions split by the addition theorems with and :
Design decisions
Three capability traits
Problem. The algorithms need more from the real scalar than
luna-generic and arithmetic traits, but not every scalar type has every
extra capability.
Options. One large “floating real” trait; no traits (hard-code
Double); a layered set.
Choice. Three traits, each a separate concern:
FloatingAnalyticScalarnames the algebraic and analytic capabilities (aFieldwithNum,Compare, constants and the real elementary functions and their inverses). It assumes nothing about IEEE 754, so an exact or interval type could satisfy it.FloatingSpecialValuesnames the IEEE 754 layer: NaN, signed infinities and the sign of zero. These have no meaning for a type without them, so they are not part of the analytic trait.FloatingBackendScalarcombines both and adds the primitives the stable algorithms below use:hypot(modulus without overflow),log1p(logarithms near one),truncandto_int(detecting integer exponents inpow) andfrom_double(constants).
Code can then require the smallest trait that states its needs, as Luna
Flow prefers over a single “real number” trait. All three are implemented
for Float and Double. The public functions are still written for
Complex[Double] and call the Double primitives directly; no function is
generic over the traits yet.
Free functions
MoonBit does not allow a package to add methods or trait instances to a
type defined in another package, so the analytic functions cannot be
methods of Complex[T]. They are free functions, @fb.log(z), and the
generic core stays free of floating-point semantics.
Modulus without overflow
The direct overflows when , although is representable, and underflows for tiny inputs. With and :
abs delegates to hypot, which uses this kind of scaling; abs_log uses
the second form, so is finite for every finite non-zero ; and
abs_sqr uses the third, which only overflows when itself does.
Stable square root
The textbook formulas
cancel catastrophically: for and , loses all digits, and for , does. The package computes only the cancellation-free quantity
where the scaled forms avoid overflow, and recovers the other part from . For the root is ; indeed, with ,
For the root is with the sign of , by the same computation with . The real part is never negative, as the principal branch requires. On the cut, is treated like , so both sides map to .
Smith’s division
The textbook quotient divides by , with the overflow problems of the core design. Smith’s method divides by the larger component first.22 R. L. Smith, “Algorithm 116: Complex division”, Communications of the ACM 5(8), 1962. For let , so :
and symmetrically with when . Only is
squared, so the denominator overflows only if the quotient does. div
computes once and multiplies by it. If has an infinite part and
has no NaN part, div follows C99 Annex G: a finite numerator gives a
zero result, and an infinite one the quotient of the infinity signs.
Exact integer powers
through rounds the angle and
the modulus , so even would not come out as
exactly . For a real integer exponent with , pow
and pow_real use binary powering instead: complex
multiplications, exact for small Gaussian integers, and . Other exponents use the polar form of : with from abs_log and ,
At , , for real , and every other exponent gives NaN.
Overflow-free tangent
, using . For large both and overflow while . Multiplying numerator and denominator by with , and using , :
tan uses this form for and the direct form below, where it
is accurate. tanh applies the same derivation with the roles of and
exchanged.
The arcsine algorithm of Hull, Fairgrieve and Tang
For , let , , and . Then33 T. E. Hull, T. F. Fairgrieve and P. T. P. Tang, “Implementing the complex arcsine and arccosine functions using exception handling”, ACM Transactions on Mathematical Software 23(3), 1997. The crossover values and are theirs.
and the other quadrants follow from oddness in each part. Both formulas lose accuracy in two regions, which the algorithm treats separately:
- When is close to (), is ill-conditioned. The real part is computed as with a quantity formed from and without subtraction.
- When is close to (), suffers from . The algorithm computes without cancellation from and and uses .
For or above , where and would overflow,
asin uses the asymptotic form , from . acos uses there.
Arctangent and inverse hyperbolic tangent
Writing with :
and gives
The ratio inside the logarithm equals with ; for the package evaluates it as
to keep accuracy near
the real axis. For large the real part is evaluated on
with . atanh is the same computation with and
exchanged, from , and
switches to when .
Inverse hyperbolic cosine by Kahan’s formula
acosh uses , built from the stable square root. Unlike
, it has the correct cut without extra
sign adjustments, because each square root has its cut where its argument
is negative real.
Exact real fast paths
sin and cos return the real function for exactly; asin,
acos, asinh, acosh and atanh dispatch real inputs to the _real
functions and purely imaginary inputs to the real inverse functions of .
This keeps results on the real line exactly real, and the _real
functions also serve callers who start from a Double.
Reciprocal functions through the core inverse
sec, csc, cot, their hyperbolic counterparts and the inverse
reciprocal functions are compositions with Complex::inv of the
core. They inherit its unscaled formula and its abort on a zero
modulus, instead of producing infinities at poles. acot special-cases
to return .
Correctness and invariants
Identities checked by the test suite
exp(log z) = z, sin(asin z) = z, cos(acos z) = z, tan(atan z) = z,
sinh(asinh z) = z and tanh(atanh z) = z at sample points in the first
quadrant, with tolerances to ; sqrt(-3 + 4i) = 1 + 2i; regressions for huge asin arguments, the atan cut, infinite
atanh and acosh inputs, division by infinity and , .
Known deviations from the principal values
The implementation uses the constant (tau) in places where the
principal value needs :
| Function and input | Returned | Principal value |
|---|---|---|
arg(z), , | (C99: by the sign of ) | |
log(z), same inputs | ||
pow, pow_real, negative real base, non-integer exponent | angle | angle |
acos(z), , | ||
acos_real(x), | ||
asec_real(x), | ||
acosh_real(x), |
Because while , these values are not even
logarithms or inverses of the input: exp(log(-1)) is , and
cos(acos(z)) is for . The test suite currently asserts the
value of arg on the negative axis. These are defects to be fixed in
the implementation; this manual records the current behaviour.
Other accuracy notes
asinevaluates in its branch where the Hull–Fairgrieve–Tang algorithm (andacoshere) uses . Close to the branch points this loses digits: at the imaginary part has a relative error near .expoverflows in before multiplying by and , soexp(710 + 0i)has a NaN imaginary part ().- The
Floatinstance oflog1pis and loses relative accuracy for ; no public function uses it yet.
Alternatives rejected
- Methods on
Complex. Not possible from a separate package, and moving the functions into the core would bring IEEE semantics into the generic type. - A single floating-real trait. It would force IEEE special values on every analytic scalar.
- Textbook formulas everywhere. Simpler, but they overflow for and cancel near the branch cuts, as derived above.
- Full C99 Annex G special-value tables. Only the cases listed in the API are handled; the remaining infinities and NaN propagate through ordinary arithmetic.
Boundaries
- Analytic functions for
Complex[Double]only; noComplex[Float]functions, although the traits are implemented forFloat. - No signed-zero selection of the side of a branch cut, except in
arg. - Reciprocal functions abort at poles instead of returning infinities.
- No error bounds are certified; accuracy is checked by regression tests.
- No checked or contextual (
Result) variants.
Footnotes
-
W. Kahan, “Branch cuts for complex elementary functions, or much ado about nothing’s sign bit”, in The State of the Art in Numerical Analysis, Clarendon Press, 1987. It also discusses how a signed zero can select the side of a cut. ↩
-
R. L. Smith, “Algorithm 116: Complex division”, Communications of the ACM 5(8), 1962. ↩
-
T. E. Hull, T. F. Fairgrieve and P. T. P. Tang, “Implementing the complex arcsine and arccosine functions using exception handling”, ACM Transactions on Mathematical Software 23(3), 1997. The crossover values and are theirs. ↩