Repository navigation
rational: Q15 mixed ratios going down at a power-of-two row gain - #65
Merged
Merged
Conversation
Normalized to unity, a mixed ratio going down's L-phase rows put their centre tap near L / M (2/3: 0.67, 3/8: 0.38), leaving part of the Q1.14 range unused, the decimator's shrinkage at a smaller factor. Their Q15 rows now sum to G x unity instead, where G is the largest power of two with G L / M < 2: 2 for 2/3 and 3/4, 4 for 3/8. finalize_divided shifts G back out exactly in the single rounding (basic_stage::k_table_gain, finalize_output). Every Q15 stage now attains the 70 dB tier at every 70 dB profile. The mixed ratios going down reach -70.0 to -72.4 dB, from -68.2 to -69.7 (3/4 at balanced: -68.2 to -72.4). One cell moves against the trend: 3/4 at economy goes from -72.8 to -70.0 dB, its tightest, still the tier. Q15 RMS from double is 4 to 10 dB lower. Q31, float and double are unchanged. The scaled rows are snapped to 2^-34 (2^-20 of a Q1.14 LSB) before quantizing. Otherwise the design's last-bit differences between libms (glibc on the host, newlib on the Cortex-M legs, measured) move a row-sum residual between the equal remainders of a symmetric pair, so one table differed between host and target. Ties are now exact and broken by index everywhere. Twelve Q15 table pins are re-pinned (hash, MACs, stopband, RMS). The DspTap pin moves to its main, which carries tap/DspTap#55 (decimate.h's Q15 tables at M h; this engine's tests use decimate.h only in float). Every ratchet count stays within +/-3 %: ratio23_q15_eco moves +0.01 % on M33, +1.9 % on M55 and 0.00 % on Hexagon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
tap
pushed a commit
to tap/DspTap
that referenced
this pull request
Oct 8, 2026
The window and the sinc are even, but the per-branch normalization is not: branch j and its mirror branch (-2 - j) mod L are summed in opposite orders, so their gains can differ in the last bit and leave the two halves of the design an ulp apart. Measured: equal under glibc and unequal under newlib for the third-band m = 8 design, which is how the 2/3 super_economy Q15 table of SampleRateTap's rational engine came to differ between the host and its Cortex-M legs (tap/SampleRateTap#65): quantize_row_preserving_sum gives a leftover LSB to the largest remainder, a mirrored pair that lands in one row ties for it, and an ulp decided which member won. design_nyquist now copies the second half from the first after the normalization, so h[i] == h[N - 1 - i] bit for bit (the contract states it; each branch still sums to 1 within double rounding). quantize.h states its tie rule, the lowest index, and why a mirrored design makes it the same on every host. Nyquist.IsSymmetricBitForBit covers every band of the vocabulary at the tiers' lengths; Quantize.TiesStepThe LowestIndex pins the rule. Anchors for the family book on the designer, the search, the sync_stage concept and chain<>::flush; comment-only. The host batteries (clang, gcc) are unchanged: no pinned table moves on glibc, where the mirror gains happened to agree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
tap
pushed a commit
that referenced
this pull request
Oct 8, 2026
…oved DspTap pinned at tap/DspTap#56: design_nyquist copies its second half from the first after the per-branch normalization, whose mirror branches were summed in opposite orders and could carry gains an ulp apart (equal under glibc, unequal under newlib for the third-band m = 8 design). That was the cause of the 2/3 super_economy Q15 table differing between the host and the Cortex-M legs in #65; the 2^-34 snap that PR added to stage.h is removed, since the ties it made exact are exact by construction now. Every Q15 pin holds. Two Q31 pins move on the host, ↓6 balanced and 3/4 economy: one LSB moves between the members of a mirror pair (branch 0 tap 8 to branch 4 tap 19; row 1 tap 11 to tap 17), the row sums unchanged, the two designs where glibc's mirror gains differed. The tables are the same on the host, the M33 and the M55. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
tap
pushed a commit
to tap/DspTap
that referenced
this pull request
Oct 8, 2026
The window and the sinc are even, but the per-branch normalization is not: branch j and its mirror branch (-2 - j) mod L are summed in opposite orders, so their gains can differ in the last bit and leave the two halves of the design an ulp apart. Measured: equal under glibc and unequal under newlib for the third-band m = 8 design, which is how the 2/3 super_economy Q15 table of SampleRateTap's rational engine came to differ between the host and its Cortex-M legs (tap/SampleRateTap#65): quantize_row_preserving_sum gives a leftover LSB to the largest remainder, a mirrored pair that lands in one row ties for it, and an ulp decided which member won. design_nyquist now copies the second half from the first after the normalization, so h[i] == h[N - 1 - i] bit for bit (the contract states it; each branch still sums to 1 within double rounding). quantize.h states its tie rule, the lowest index, and why a mirrored design makes it the same on every host. Nyquist.IsSymmetricBitForBit covers every band of the vocabulary at the tiers' lengths; Quantize.TiesStepThe LowestIndex pins the rule. Anchors for the family book on the designer, the search, the sync_stage concept and chain<>::flush; comment-only. The host batteries (clang, gcc) are unchanged: no pinned table moves on glibc, where the mirror gains happened to agree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
tap
pushed a commit
that referenced
this pull request
Oct 8, 2026
…oved DspTap pinned at tap/DspTap#56: design_nyquist copies its second half from the first after the per-branch normalization, whose mirror branches were summed in opposite orders and could carry gains an ulp apart (equal under glibc, unequal under newlib for the third-band m = 8 design). That was the cause of the 2/3 super_economy Q15 table differing between the host and the Cortex-M legs in #65; the 2^-34 snap that PR added to stage.h is removed, since the ties it made exact are exact by construction now. Every Q15 pin holds. Two Q31 pins move on the host, ↓6 balanced and 3/4 economy: one LSB moves between the members of a mirror pair (branch 0 tap 8 to branch 4 tap 19; row 1 tap 11 to tap 17), the row sums unchanged, the two designs where glibc's mirror gains differed. The tables are the same on the host, the M33 and the M55. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
tap
pushed a commit
that referenced
this pull request
Oct 8, 2026
…oved DspTap pinned at tap/DspTap#56: design_nyquist copies its second half from the first after the per-branch normalization, whose mirror branches were summed in opposite orders and could carry gains an ulp apart (equal under glibc, unequal under newlib for the third-band m = 8 design). That was the cause of the 2/3 super_economy Q15 table differing between the host and the Cortex-M legs in #65; the 2^-34 snap that PR added to stage.h is removed, since the ties it made exact are exact by construction now. Every Q15 pin holds. Two Q31 pins move on the host, ↓6 balanced and 3/4 economy: one LSB moves between the members of a mirror pair (branch 0 tap 8 to branch 4 tap 19; row 1 tap 11 to tap 17), the row sums unchanged, the two designs where glibc's mirror gains differed. The tables are the same on the host, the M33 and the M55. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
finalize_dividedshifts G back out exactly in the single rounding.basic_stage::k_table_gainandfinalize_output()carry it, the same mechanism as the Q15 decimators.main, which carries decimate: Q15 stores M·h, the M divided out in finalize_divided DspTap#55:decimate.h's Q15 tables at M·h. This engine's tests usedecimate.honly in float.stage.hcontract, README and CLAUDE.md.Why
You asked me to model the Q15 mixed-down limit and build it only if it reached the 70 dB tier. Rows normalized to unity put the centre tap near L/M (0.67 for 2/3, 0.38 for 3/8). That leaves part of the Q1.14 range unused: the same shrinkage as the decimators', at a smaller factor.
Attained Q15 stopband, measured by the test on the 16,384-point grid:
Verification
-Werror. gcc-Werrorpasses the rational label (113/113). clang-tidy and clang-format are clean.ratio23_q15_ecochanges by +0.01 % on M33, +1.9 % on M55 and 0.00 % on Hexagon.Notes for the reviewer
quantize_row_preserving_sumgives each leftover LSB to the tap with the largest remainder. A symmetric design has pairs of exactly equal remainders, and the design's last bits differ between glibc and newlib, so the tie broke differently.quantize.h, which would move pins family-wide, so I've left it as a finding for you to decide.🤖 Generated with Claude Code
https://claude.ai/code/session_015VR1VC4SDGxHZQQsQvPBaA
Generated by Claude Code