db151cd6

By: ValentinHirschi <valentin.hirschi@gmail.com>

Make native DoubleFloat products commutative for exact predicates

NinjaRS found an equal-mass four-mass D0 (m² = mu² = 1; p1² = 2171/144,
p2² = 211/16, p3² = -5/4, p4² = -587/72, s12 = -311/72, s23 = 27/16) whose
native DoubleFloat finite part was +0.372256 instead of -0.0634209 in one
of its eight relabellings, and at the rounded invariants its reduction
passes. The imaginary part was right. Binary64 (hand-written and
generated), native Float and the expression interpreter agreed in every
labelling. In NinjaRS this moved a 12-point box-stage sum by 3.5e-14
(relative) and the cancelling DoubleFloat amplitude by 1.6e-3.

The compensated product of xprec's Df64, behind numerica's DoubleFloat,
accumulates its cross terms in operand order, so x*y and y*x can differ in
the low component. The imaginary part of z conj(z), re (-im) + im re, then
kept a residue (1.2e-35 here), and the exact sign test x - |x| of the
sheet dilogarithm's inversion `negative(1 - z conj(z))` read it as
negative: it inverted an argument of modulus 0.09. Binary64 and MPFR
products are commutative, so their residue is exactly zero (as
tests/native_primitives.rs already checks for binary64).

The native DoubleFloat complex product now forms its real products from
operations that are each symmetric in the two operands: the high product
with its fused rounding error and the plain sum of the two cross products
(Dekker's form, the same few-ulp accuracy). Products are then exactly
commutative and odd in each operand, and z conj(z) is exactly real.
Binary64 and Float are unchanged. The interpreter's DoubleFloat route still
uses numerica's product.

Tests: tests/native_double_float_d0.rs (all eight labellings at exact and
at NinjaRS's rounded invariants, against native Float at 256 bits, Float
at 128 bits and binary64) and a DoubleFloat case in
tests/native_primitives.rs. Both fail without the change.

Retired instructions per call (in-process counters, DoubleFloat, measured
on 264a6ce): complex multiply-add 353 -> 269; 32 generic rows D0 -6.3%,
C0 -8.5%; the eight equal-mass rows above D0 -23% and C0 +9.8%, where
exact decisions now select other branches.

A sweep of native DoubleFloat against native Float at 128 and 192 bits over
the scalar rows of parity, scalar_audit, permutation_regressions,
real_mass_limits, benchmark and ninja_master_rows (1388 rows) flags the
same rows with and without the change: three on-shell C0 rows of
permutation_regressions (lines 109-111) where native Float keeps 5 of 192
bits and DoubleFloat matches the OneLOop reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub
configure