float-pipeline-investigation.md (10297B)
Binary64 investigation — blocked significands and rounded pipelines
Third screen, following layout access and significand kernels. No public encoding, math API, production compiler or reducer changes.
What was measured
bench/float-pipelines.tri compares existing canonical LSB-first naturals with
radix-256 little-endian lists of canonical natural octets. Empty list means
zero; only the highest limb must be nonzero. Intermediate low zero limbs remain
present. A limb is still a tree, not a machine word.
The blocked implementation has its own addition with carry, nonnegative subtraction with borrow, schoolbook multiplication, width calculation, right-shift/sticky extraction, increment, and nearest/ties-to-even rounding. It does not convert entire operands to naturals for arithmetic. Multiplication uses natural arithmetic within each bounded digit product/carry. This is one blocked candidate, not a claim that every limb size or algorithm has been tested.
All arithmetic pipelines calculate an exact unsigned integer result, round it to at most 53 significant bits, and return:
(removedBinaryScale, (roundedSignificand, inexact))
A carry into bit 54 is renormalized without a second rounding. After rounding, a fixed-position bit check suffices to detect that carry; both candidates use this rather than recomputing the full width. Before rounding, width is computed from the exact result in each layout.
Measured operations:
- same-scale addition and nonnegative subtraction;
- exact 53-by-53-bit multiplication followed by 53-bit rounding;
- three successive rounded multiplications, accumulating scale and inexact flags;
- bounded exact alignment followed by addition/subtraction and rounding;
- blocked rounding at small, byte-aligned, near-width, and far shift counts;
- conversion-only controls, separate from arithmetic.
The arithmetic output remains in its candidate layout. Input conversion is not silently charged to only one side. Both pipelines preserve their own layout through chained operations.
Scope boundaries
These are unsigned significand pipelines, not complete binary64 operations.
Scale is relative to a common input unit; there is not yet a signed exponent,
subnormal-range clamp, infinity/NaN handling, signed-zero policy, or division.
Subtraction inputs satisfy the internal precondition a >= b after alignment.
Alignment controls construct an exact shift with gaps bounded to 0..54. They
are deliberately not proposed as the production algorithm for arbitrarily
large exponent gaps: the eventual float implementation must use guard/sticky
bounds for that case. Separately tested right shifts do terminate promptly at
the end of a finite input even when the shift count is 2^128.
The three-multiply chain is rounded after each operation. It is not a fused expression or a single final rounding of the exact triple product.
Avoiding an unfair blocked implementation
An initial blocked rounding formulation shifted by k-1 to expose the guard
bit and then shifted again. That forced unnecessary residual-bit reconstruction,
particularly when k was byte aligned. The recorded candidate instead extracts
guard/sticky directly from the boundary octet and builds the quotient once.
Byte-aligned quotients can reuse an input suffix. The original split formulation
remains an experimental control, not the implementation used by recorded
limbRound or arithmetic timings.
This distinction changed byte-aligned rounding from an apparent weakness to a strength. It reinforces the earlier lesson: compare credible implementations, not just the first translation of an algorithm into each layout.
Validation and reproducibility
test/float_pipelines.py stages all sources and selects the shipped stable
compiler explicitly. The successful recorded run checked:
- 819 shift/round inputs against exact-integer expectations for blocked shift and blocked rounding;
- 257 arithmetic input pairs for both layouts, each through add, subtract, multiply and three-step chain;
- 234 aligned input triples through both aligned operations in both layouts;
- 18 direct final-rounding boundary values in both layouts, including carry into bit 54 and below/tie/above cases near a power of two;
- conversion in both directions and limb widths;
- all 260 measured invocations against their complete expected result trees.
There are 185 validation invocations (many contain batches) and 52 measured workload groups. Each timing group has five fresh-process repetitions, with the first excluded from summaries. Timed batches contain 16 values. Inputs include random normalized significands, all-one/carry patterns, and cancellation to zero or a small difference. Shift tests include 53- and 106-bit values.
test/float_pipelines_oracle_test.py independently compares the Python oracle
against binary64 host integer conversion over 642 values, normalized products
and chains over 256 pairs, and explicit carry cases. It requires a radix-2,
53-bit Python float host and skips otherwise. This supplements, rather than
replaces, exact-integer expectations. The prior Fraction-based rounding-oracle
suite also passes.
Timing runs from invocation start through sharing-aware full-result forcing,
before rendering. Compilation/loading are outside this interval; profiling
collection overhead is included. Input forcing not already done by loading may
contribute. Each process has a 2 GiB heap limit and a time budget. Randomized
execution order, raw samples, ranges, provenance hashes, and medians are retained
in bench/float-pipeline-results.json. The raw run directory is
/tmp/tricu-float-pipelines-boundaries.
Recorded results
All times below are batch medians for 16 values, not scalar operation times. MB means cumulative decimal megabytes allocated, not peak/live memory.
Random normalized 53-bit significands
| Pipeline | LSB ms / MB | Radix-256 ms / MB |
|---|---|---|
| Add then round | 9.69 / 48.16 | 23.50 / 125.50 |
| Subtract then round | 10.80 / 58.68 | 14.72 / 81.04 |
| Multiply then round | 118.08 / 770.68 | 176.94 / 1249.91 |
| Three rounded multiplies | 331.19 / 2322.86 | 536.22 / 3766.58 |
LSB also uses less allocation for the recorded carry-heavy, cancellation, and bounded-alignment pipelines. Cancellation timings are small/noisy enough that close rankings should not be treated as universal performance laws. The allocation differences are more stable.
Carry-heavy multiplication costs much more in both implementations: LSB 250.91 ms / 1689.16 MB versus blocked 322.92 ms / 2232.40 MB. Input shape matters; random products alone would understate worst-case work.
Rounding 53-bit significands
| Shift regime | LSB ms / MB | Radix-256 ms / MB |
|---|---|---|
| Small: 1, 2, 3, 7 | 1.47 / 3.51 | 13.28 / 73.53 |
| Byte aligned: 8, 16, 24, 32 | 4.39 / 18.62 | 1.48 / 3.65 |
| Near width: 52, 53, 54 | 8.67 / 47.39 | 3.07 / 15.64 |
Far: 2046 and 2^128 |
8.63 / 47.23 | 2.18 / 6.00 |
These LSB shift comparisons are uncached. The previous investigation already showed that known widths eliminate most far-gap scanning. The blocked far-gap result is therefore not evidence that it beats a metadata-aware LSB float implementation. Byte-aligned suffix reuse, however, is a genuine advantage of this blocked layout for that workload.
Interpretation and current direction
- Existing LSB naturals remain the strongest general working-significand candidate among those tested. The earlier kernel advantage survives these arithmetic pipelines; it was not merely a field-access artifact.
- Radix-256 earns its place as a serious candidate, especially for byte-aligned movement, but it does not win overall here. Fewer limbs do not imply less reduction when every digit operation is still implemented with trees.
- Metadata-aware bounds and specialized bit movement remain useful regardless of layout. Neither a cached width nor a conversion should be treated as free.
- Absolute multiplication cost is substantial: the random LSB pipeline averages roughly 7.4 ms and 48 MB of allocation per value in this small-batch screen. These are not production throughput guarantees or lower bounds, but they make multiplication a clear tuning target. Correctness alone does not yet make this an application-ready numerical library.
Remaining decision work
The subsequent multiplication/source-form/demand investigation compares nine multiplication strategies and isolates a full-adder demand counterexample plus a corrected source-form improvement. The reducer and production compiler remain unchanged; LSB is still a control, not a commitment.
The evidence now supports making LSB the primary implementation candidate rather than giving every representation equal attention indefinitely. Before freezing a public representation or claiming IEEE conformance:
- tune/profile the multiply-and-round path, including known-width specialization and alternative multiplication algorithms where their overhead is justified;
- implement bounded exponent alignment with cancellation-safe addition and subtraction, rather than extrapolate the exact-alignment controls;
- add correctly rounded division and full binary64 boundary behavior, including subnormal transitions and overflow;
- measure retained-memory/demand behavior and whole-operation conversion costs;
- decide public value invariants and exceptional-value policy separately from the internal working representation.
No host IO or host floating-point arithmetic is used by the candidate workers. A future host service remains possible through the separate exact IEEE-bit interchange boundary discussed in the first investigation.
Reproduce
HOST=$(nix build .# --no-link --print-out-paths)/bin/tricu
python3 test/float_significands_oracle_test.py
python3 test/float_pipelines_oracle_test.py
python3 test/float_pipelines.py "$HOST" \
--directory /tmp/float-pipelines-new --size 16 --repeats 5
Use a fresh output directory and do not disable Python assertions. Failed runs
retain diagnostics with complete: false rather than being counted as samples.