core design
This page derives the algebra of Complex[T], states when each
luna-generic instance is lawful, and explains the choices behind the
generic type: mutable fields, the unscaled division formula, and the
separation from floating-point analysis.
Design goal
One generic complex type for every scalar of the ecosystem, whose instances advertise exactly the algebraic structure the construction has, and which leaves IEEE special values, branch cuts and transcendental functions to a backend package.
Mathematical background
The construction
For a commutative ring , the complex numbers over are the quotient of the polynomial ring by the ideal generated by :
Division by the monic polynomial leaves a unique remainder of
degree below two, so every element is with unique :
the pair (re, im). Computing with polynomials and reducing
gives the operations of the package:
As a quotient of a commutative ring, is a commutative ring, with zero and one ; embeds as .
Conjugation and the norm
is the map induced by , which fixes the ideal , so it is a ring automorphism of order two:
The norm lies in and is multiplicative, .
Inverses and division
If is invertible in , then , so
Conversely, if is invertible then , so is invertible. Hence is a unit exactly when is.
When the construction is a field
Let be a field. is a field exactly when is irreducible over , that is when is not a square in . Directly: a non-zero with must have , and then . Conversely, if then exhibits zero divisors.
- For (
Float,Double), is not a square and is a field. - For (
Complex[Double]), is a square, soComplex[Complex[Double]]is a ring with zero divisors, such as .
Design decisions
Instances follow the structure of T
Problem. Which luna-generic traits may Complex[T] implement?
Choice. Zero, One, AddMonoid and AddGroup need only the
corresponding operations of T, because they act componentwise. MulMonoid,
Semiring and Ring need T : Ring, because the product uses subtraction
() and the ring laws of need those of . Inverse,
MulGroup, Field and the Div operator need T : Field for .
Conjugate needs only Neg.
The Field instance is declared for every T : Field. By the previous
section that is lawful when is not a square in T, which covers the
real scalar types; for T = Complex[Double] it is not, and inv aborts on
non-zero zero divisors. A trait bound cannot express “−1 is not a square”,
so the instance trusts the caller here.11 This is a known gap in the “implement only lawful instances”
rule of Luna Flow. The test suite uses nested complex numbers only on
values with an invertible norm.
Four multiplications
Options. The schoolbook product with four multiplications; Gauss’s three-multiplication form , , , product .
Choice. Four multiplications. Over floating point the schoolbook form
has a normwise relative error of at most ,22 R. Brent, C. Percival and P. Zimmermann, “Error bounds on complex
floating-point multiplication”, Mathematics of Computation 76 (2007). while the
three-multiplication form adds cancellation-prone sums such as and
and is less accurate componentwise; and for generic T a multiplication is not
necessarily more expensive than an addition.
Componentwise, with , so
which is small relative to the result unless and cancel.
Textbook division in the generic core
Problem. Division needs ; for floating point, overflows or underflows long before does.
Options. Scaled algorithms (Smith’s, or rescaling by a power of two) need comparisons and absolute values that a generic field does not have; the textbook formula needs only field operations.
Choice. The generic Div and Inverse use the textbook formula with
Inverse::inv of T. It is exact for exact fields. For Double it is
correct while stays in range, and Inverse of Double aborts
on zero, including an underflowed norm. Scaled, special-value-aware
division lives in float_backend.
Mutable fields
Problem. Complex values are often updated in loops (accumulators, recurrences), where allocating a new value per step is wasteful.
Choice. Complex[T] is a pub(all) struct with mut re and mut im
plus set, set_re and set_im. Every operation returns a new value and
never mutates its inputs, so code that does not call the setters can treat
complex numbers as values. Callers that do mutate must remember that the
struct is shared by reference.
No analytic functions in the core
, or need an order, absolute values,
transcendental functions of the real scalar, a choice of branch, and IEEE
special values. None of these exist for a generic field. The root package
therefore stops at algebra, and the float_backend
package supplies analysis for Double.
Text and debug forms
Show prints re + imi using T’s text (1 + -2i for ), which
is the canonical text of the type and the reason to_string stays a
promoted method. Debug prints the record form for tests and diagnostics.
MoonBit 0.10 promotion of the other trait methods is explicit in
src/extends.mbt.
Correctness and invariants
- Ring laws hold for
T : Ringbecause is a quotient ring; the test suite checks associativity, identities and on integers. z * z.conjugate()equals exactly for exactT.z * w / w == zandz.inv() * z == 1hold for exact fields when , are units, and up to rounding forDoublewithin range.- No operation mutates its arguments; only the three setters do.
Alternatives rejected
- A
Double-only complex type. It would duplicate the type forFloat, integers (Gaussian integers) and exact rationals. - Immutable fields. Cleaner value semantics, but a breaking change for
existing callers that update
reandimin place. - Scaled division in the core. It needs order and magnitude operations that generic fields lack.
Boundaries
- No analytic or transcendental functions, no special-value handling, no branch cuts; see float_backend.
- No polar representation in the core.
- No check that
Tmakes a field; theFieldinstance trusts it. - No overflow-safe division for floating-point
T.
Footnotes
-
This is a known gap in the “implement only lawful instances” rule of Luna Flow. The test suite uses nested complex numbers only on values with an invertible norm. ↩
-
R. Brent, C. Percival and P. Zimmermann, “Error bounds on complex floating-point multiplication”, Mathematics of Computation 76 (2007). ↩