blob: e6299fd79c0ef0e388d874743d5810ff7ab70cda [file] [view]
# Numerical Code
Use this guidance when changing scalar math, packet math, decompositions, eigensolvers, linear solvers, matrix
functions, or numerical tests. The nearby implementation, tests, and public documentation in the checked-out tree
are the source of truth; this file defines the review standard rather than an algorithm.
## Standards and Accuracy Contracts
- Follow the applicable ISO C++ and incorporated ISO C library contracts. IEEE 754 requirements apply where the
platform and API claim IEC 60559 behavior. cppreference is a useful secondary summary, not a normative
specification.
- Distinguish exact semantic requirements from approximation quality. NaN, infinity, signed zero, domain errors,
and function-specific boundary behavior must follow the contract. For ordinary finite inputs, the C++ standard
generally does not promise correctly rounded elementary functions, so Eigen's documented or established ULP
budget is the relevant target.
- Do not use `VERIFY_IS_APPROX` as the acceptance criterion for a newly designed numerical kernel. Its defaults in
[`test/numerical_test_helpers.h`](../test/numerical_test_helpers.h) are deliberately loose test-framework
tolerances, not machine-epsilon or ULP bounds.
- Scale coverage with the change. A narrow fix needs focused regression cases and nearby coverage; a new algorithm
or shared kernel needs broad conditioning, scalar-type, and backend coverage.
## Scalar Math
Test regular inputs across the full supported domain and concentrate samples near discontinuities, roots, extrema,
range-reduction boundaries, overflow and underflow thresholds, and difficult rounding cases. Use an error metric
that matches the contract:
- Use ULP error when evaluating a floating-point approximation against a correctly rounded or high-precision
reference. Relative error is not meaningful near zero, and absolute error alone hides scale-dependent failures.
- Use MPFR for ground truth when an accuracy decision depends on the reference. Eigen's C++
[`ULP accuracy tool`](../test/ulp_accuracy/README.md) supports MPFR and standard-library references. The
[`coefficient-wise math table`](../doc/CoeffwiseMathFunctionsTable.dox) records existing accuracy expectations.
- Sollya is appropriate for polynomial or rational approximation design. Record the function, domain, precision,
error objective, tool version, and generation command or script so coefficients are reproducible; verify the
emitted implementation independently with MPFR.
Test special values explicitly: `+0`, `-0`, positive and negative infinity, quiet NaN, normal/subnormal boundaries,
the smallest subnormal, and values immediately on both sides of each domain boundary. Approximate comparisons can
treat two NaNs as matching and cannot distinguish the sign of zero. Therefore use explicit predicates:
- Check NaN with `(numext::isnan)(value)`.
- Check infinity with `(numext::isinf)(value)` and check its sign separately.
- Check zero by equality and its sign with `(numext::signbit)(value)`.
- Check finite classification when overflow or invalid results are possible.
## Decompositions and Solvers
Prefer backward-error and invariant checks over forward comparison with one reference answer. Depending on the
operation, test normalized reconstruction error, solve residual, eigenpair residual, orthogonality/unitarity, rank,
symmetry, or structure preservation. Express tolerances as named bounds derived from
`NumTraits<RealScalar>::epsilon()`, dimension, and the expected operation count; avoid unexplained decimal literals.
Forward error is condition-dependent. A well-conditioned problem may support a tight result comparison, while a
near-singular problem can have a small residual and a large forward error. Estimate or bound conditioning when a
forward comparison is necessary, and do not reject a stable answer merely because a different stable algorithm
selects different vectors, signs, phases, pivots, or bases for a clustered invariant subspace.
Exercise structures relevant to the algorithm: well-conditioned, ill-conditioned, near-singular, singular,
rank-deficient, clustered/repeated spectra, extreme scaling, and the matrix properties promised by the API. Useful
families include Hilbert, Vandermonde, Wilkinson, Toeplitz/KMS, banded, defective or near-defective, and barely
positive-definite matrices. Check error/status reporting as well as successful results.
For uniform transformations and built-in operations on small structured submatrices, use fixed-size block expressions,
such as `T.template block<2, 2>(i, i).determinant()`, so the mathematical structure and compile-time dimensions remain
explicit. This is especially useful for Schur blocks, pivots, and small panels; use coefficient access instead when the
entries require distinct formulas.
Where LAPACK has a counterpart, require comparable backward stability, conditioning behavior, pivoting robustness,
and test-category coverage. Do not require identical internal steps, pivot order, eigenvector signs/phases, or
roundoff-level output. Higham's *Accuracy and Stability of Numerical Algorithms* and Golub and Van Loan's *Matrix
Computations* are standard references for choosing error measures and adversarial inputs.
## Packet Accuracy
- Test the scalar path, generic packet fallback, and every affected backend specialization that is available. Build
and run [`test/packetmath.cpp`](../test/packetmath.cpp) and, for special functions,
[`unsupported/test/special_packetmath.cpp`](../unsupported/test/special_packetmath.cpp). Report backends that were
not available locally.
- Compare packet results with the scalar contract for special values, but use MPFR rather than assuming the scalar
standard-library result is accurate enough to set a new finite-input ULP target.
- Cover every lane, mixed regular/special lanes, alignment and tail cases where applicable, and values around
approximation-region boundaries. A packet implementation must not let one lane's special value affect another.
- Treat a few-ULP performance tradeoff as a measured, documented finite-input decision. It does not waive NaN,
infinity, signed-zero, or domain semantics unless the API and build mode explicitly document different behavior.
## Documented Bounds And Shortcut Paths
A scaling threshold or overflow budget stated in a comment is part of the code: when the operation it bounds widens
(a multiply path gains a divide, a growth factor becomes a parameter), re-derive the bound rather than carrying the
old expression forward. Early exits and length-one shortcuts must satisfy the same invariant as the general path —
they are where a guard added later tends not to reach — so give them regression coverage at the boundary they handle.
## Subnormals and Flush-to-Zero
Require gradual-underflow behavior when the target and active floating-point mode support it. Some targets or build
modes have fixed or enabled flush-to-zero (FTZ) behavior, so an impossible subnormal expectation must be detected and
conditionalized rather than made flaky. Use the facilities and platform notes in
[`test/fp_control.h`](../test/fp_control.h) and nearby packet tests.
Keep an FTZ exception narrow: document the affected target and operation, preserve and restore controllable FP
state, and still verify normal values, NaN, infinity, signed zero, and scalar/packet consistency in that mode. Do not
use FTZ as a blanket reason to skip underflow tests or to hide accidental compiler flags that changed semantics.
## Provenance
Learn algorithms from published papers, standards, and textbooks, then write an original Eigen implementation. Cite
the specific reference inline by author/year and algorithm, routine, paper, or working-note identifier. If adapting
source code rather than an idea, first confirm that its license and provenance are compatible with Eigen. A citation
does not make copied expression from an incompatible or unknown source permissible, and attribution must never be
invented. Include the numerical rationale for non-obvious scaling, pivoting, stopping, and tolerance choices.