mixle.engines.extended module¶
Fast extended precision via error-free transformations – the “big boy math” end of mixle’s numeric spectrum, with no mpmath/gmpy2 in the hot path.
mpmath and gmpy2 are per-object and non-vectorized (~1000x slower than float64 at ~100-bit
precision); they belong in tests as a correctness oracle, never in compute. The fast way to exceed
float64 is error-free transformations (Dekker/Knuth TwoSum / TwoProd): represent a number
as an unevaluated sum of float64 components (hi + lo) and carry the rounding error explicitly.
Every operation is a handful of float64 ops that vectorize over numpy arrays, so a double-double
(~106-bit mantissa, “fp128”) costs ~5-25x a float64 op – versus ~1000x for mpmath at the same
precision.
This module provides the double-double primitives and the two reductions that matter for mixle’s EM
hot paths – an accurate sum and dot product – where catastrophic cancellation (E[x^2]-E[x]^2,
log-sum-exp of near-equal terms) otherwise eats precision. Beyond double-double, quad-double / multi-limb
take over (a Cython/C job); this file is the part that needs only numpy.
The Veltkamp split is power-of-two scaled for large operands, so every finite representable product either produces a finite error-free pair or fails closed when the exact result cannot be represented.
- two_sum(a, b)[source]
Error-free transformation of a sum: returns
(s, e)witha + b == s + eexactly.Knuth’s TwoSum – no assumption on the relative magnitudes of
aandb. Vectorized.
- quick_two_sum(a, b)[source]
Error-free sum assuming
|a| >= |b|(Dekker). One fewer op thantwo_sum().
- two_prod(a, b)[source]
Error-free transformation of a product:
(p, e)witha * b == p + eexactly (Dekker).Uses the Veltkamp split because numpy exposes no fused-multiply-add. Vectorized.
A product of two non-zero operands that underflows all the way to zero fails closed: this transform’s contract is that
p + ereproducesa * bexactly, and(0, 0)does not. A reduction over many such products is a different question – seedd_dot().
- class DoubleDouble(hi, lo=0.0)[source]
Bases:
objectAn (almost) ~106-bit float as two non-overlapping
float64arrayshi + lo.Scalars or numpy arrays; operations broadcast. The invariant is
|lo| <= 0.5 * ulp(hi). A component may be+infor-inf– exactly representable infloat64, carried withlo == 0– but never NaN, and never an overflow of finite operands.- Parameters:
hi (Any)
lo (Any)
- dd_sum(x)[source]
Accurate sum of a
float64array in double-double precision – vectorized, no Python loop over elements.Pairwise tree reduction with an error-free
two_sum()combine at every node:O(n)work inO(log n)vectorized passes, accumulating the rounding error into thelocomponent. The result is correct to ~106 bits even for catastrophically cancelling inputs thatfloat64sums get wrong.An infinite term short-circuits to the IEEE answer (
-inffor a zero-probability log-density) instead of raising; NaN, and+infmixed with-inf, still fail closed – see_infinite_sum().- Parameters:
x (Any)
- Return type:
DoubleDouble
- dd_dot(a, b)[source]
Accurate dot product
sum(a_i * b_i)in double-double precision.Uses the compiled hardware-FMA kernel when available (one
fmaper element, ~3x faster than the pure-numpy Veltkamp-split path, and exactly as accurate – each product’s error is exact either way, though the two paths accumulate in different orders, sequential versus pairwise tree, so their results agree to double-double precision rather than bit for bit). Otherwise each product is split error-free by_product_and_residual()and the products + errors are summed bydd_sum(). Defeats the cancellation that wrecks a naivefloat64dot.Every check that decides whether an input is acceptable is made once, before dispatch, so the two implementations cannot drift apart on what they accept: equal length (see
_require_equal_length(),ValueError), finite operands (ValueError), and a result that is representable at all (OverflowError). A single product that underflows to zero is not one of them: it contributes less than one smallest-subnormal to the sum, so it is summed as the zero it rounds to rather than failing the whole reduction –dd_dot([1e-200, 1.0], [1e-200, 1.0])is1.0, which is the right answer, not an error. (two_prod()on its own still fails closed there, because a lone product has nothing to be negligible against.)