arithmetic design
Design goal
Linear-algebra algorithms need a handful of scalar operations beyond the ring
operators: an absolute value for pivoting, a square root for norms, a
comparison for ordering, and a way to compare floating-point results. The
arithmetic package names these operations for linear-algebra code without
claiming that the scalar types satisfy laws they do not satisfy. It reuses the
upstream vocabulary wherever it exists and adds a trait only where none does.
Mathematical background
Operations versus structures
A structure trait such as Field promises equations: associativity,
distributivity, inverses. An operation trait such as Sqrt promises only
that a function exists. The distinction matters
because the builtin floating-point types implement the operations but satisfy
the equations only approximately: for Double,
so in general. Every trait of this package is an operation trait.
Partial operations and their totalizations
Division and square root are partial functions on the reals: is defined on and on . IEEE 754 makes them total by returning special values (, NaN), which then propagate silently. A checked operation instead totalizes a partial function into
where is a set of error values and says why is outside the
domain. CheckedDiv uses the following domain decomposition for floating-point
operands:
| Region | IEEE result | Checked result |
|---|---|---|
| , not both infinite | Ok of the same value | |
| NaN | DomainError | |
| both infinite | NaN | DomainError |
| , | DivisionByZero |
Ordering with NaN
Compare on Double is not a total order: NaN is neither less than, equal to,
nor greater than any value, so sorting or pivot selection on data containing
NaN gives order-dependent results. CheckedCompare restricts the comparison to
the ordered subset and reports UnorderedComparison outside it, which makes
the result a total order on its domain.
Approximate equality
ApproxEq uses the absolute rule .
This relation is reflexive and symmetric but not transitive. From
and the triangle inequality
gives only
and the bound is attained: with , ,
we have , and . So approx_eq is
not an equivalence relation, and the package does not present it as one.
An absolute tolerance is also not scale-invariant. Floating-point spacing
grows with magnitude: adjacent Double values near are about
apart. Near that spacing is about ,
so two different values can never be within ; near every
pair of values is. A relative rule
fixes the scale problem but fails near
zero, which is why careful code combines both; this package leaves that choice
to the caller.
Design decisions
Reuse upstream names
The scalar traits Zero, One, Inverse, Conjugate and the analytic
traits of Luna-Flow/arithmetic are re-exported with pub using rather than
redefined. A local copy would create a second, incompatible Sqrt, and a
scalar type implementing the upstream trait would not satisfy the local one.
With pub using, @la_arithmetic.Sqrt is @lf_arith.Sqrt.
Small local traits
Abs, ApproxEq, CheckedDiv, CheckedSqrt and CheckedCompare exist
because linear-algebra code wanted these names when the upstream package did
not provide them in this form. Each is one method, so a scalar type can opt in
to exactly the operations it supports. The checked traits delegate to the
upstream checked traits for Float and Double, so the two layers agree on
every input.
Context accepted and ignored for binary floating point
checked_div and checked_sqrt take an ArithmeticContext so that one
signature serves fixed-precision and arbitrary-precision scalar types. For
Float and Double the precision is fixed by the hardware format and rounding
is round-to-nearest-even, so the context has no effect.
Fixed absolute tolerances
ApproxEq uses for Double and for Float. These are
about and for the unit roundoff of each format (
and ), so they suit values of order one, such as entries of normalized vectors.
For other scales, compare with an explicit tolerance in your own code.
Correctness and invariants
- For
FloatandDouble,checked_div(x, y, ctx)returnsOk(v)exactly when IEEE division returns a value that is not produced from an invalid or divide-by-zero exception, and thenvequals the IEEE quotient bit for bit. checked_sqrt(x, ctx)returnsOk(√x)for and for NaN, andErrfor . Note that holds, sochecked_sqrt(-0.0)isOk(-0.0).checked_compareis antisymmetric on its domain:checked_compare(a, b) == Ok(k)implieschecked_compare(b, a) == Ok(-k).approx_eqis reflexive and symmetric for non-NaN values and false for NaN.
Alternatives rejected
- A local
RealorNumbertrait combining all operations. It would hide which operations an algorithm uses and force exotic scalars to implement everything. - Making
approx_eqpart ofEq.Eqmust be an equivalence relation; approximate equality is not transitive. - Relative tolerances in
ApproxEq. They need a policy near zero that depends on the application; the trait stays simple and documented instead.
Boundaries
arithmetic defines no vector, matrix or backend types and does not depend on
the other packages of this repository. It does not define algebraic structure
traits (those come from luna-generic), does not choose tolerances for matrix
algorithms (that is the Tolerance trait of mutable), and does
not implement arbitrary-precision arithmetic.