tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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:

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:

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

  1. 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.
  2. 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.
  3. Metadata-aware bounds and specialized bit movement remain useful regardless of layout. Neither a cached width nor a conversion should be treated as free.
  4. 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:

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.