Conversation
… contract
Adds tests/test_fft_fixed.cpp (Part 9's fixed-point battery: exponents,
exact fixed scale, saturation sweep, round trip per policy, BFP against
fixed, rounding symmetry, Q15 vs Q31, the Welch noise-floor model and the
twiddle-table pins) typed over {Q15, Q31} x {fixed, block_floating}, and
tests/support/sample_scale so the shared generators land in the fixed
profiles. Every measured pin is 0.0 (red) until the real kernel supplies it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
…rofiles test_fft.cpp's typed contract suite runs on float, double, Q15 and Q31 through a per-profile trait (input conversion, forward scale from the returned exponent, tolerance), with Q15TracksDouble / Q31TracksDouble beside FloatTracksDouble; the float/double comparisons are unchanged. test_fft_oracle.cpp gains profile<int16_t> / profile<int32_t> with a tolerance derived from fft_arith.h's rounding count. test_fft_rt.cpp is typed over the six FFT classes (both scaling policies) for noexcept, the allocation guard and copy bit-identity including the exponent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Measured 2026-09-18 on x86-64 Linux (GCC 13.3.0 and clang 18.1.3 agree) against claude/wave2-stage3b-kernel a55a14f and pinned at 2x: the saturation-sweep deviation, round-trip error, BFP-vs-fixed shift agreement, negation asymmetry and bias, Welch-model ratios for white noise and an on-bin tone, Q15-vs-Q31 agreement, the Q15/Q31 contract tolerances, Parseval and TracksDouble numbers, and the Q1.30 table checksums for N = 256 / 512 / 2048. The Welch model is refined to the kernel's stated structure (no rotation on the L = 4 and radix-2 stages, q = 0 skips, post-pass propagation gain 1, folded Q31 pre-shift, exact shift variances); every pinned test prints its measurement before it asserts; the inverse-exactness premise carries pre + 3 zero bits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Owner
Author
|
Kernel side: merged Generated by Claude Code |
newlib's printf on the QEMU legs has no %zu: it printed "zu", left the varargs desynchronised, and the next %s HardFaulted the M55 run inside RoundingBiasOnNegatedInputIsBounded. Sizes now go through %lu as unsigned long and the 64-bit table checksums print as two %08lx halves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
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
The Part 9 test battery for the Q15 / Q31 real FFT profiles under both scaling policies (Stage 3b of
docs/audit-fft-and-code-smells.md), written test-first against the API contract and stacked on the kernel PR (#27). Newtests/test_fft_fixed.cpp;tests/test_fft.cppwidened to four profiles;tests/test_fft_oracle.cppandtests/test_fft_rt.cppextended to the fixed profiles; one shared helper (tap::dsp::test::sample_scale) intests/support/signals.h. Noinclude/, docs, bench or workflow files are touched.Why
The kernel's header cites contract numbers; this is the battery that pins them, so a wrong kernel cannot slip through: every exponent, scale, saturation bound, noise floor, rounding property and table checksum is asserted, and every tolerance is a measured number pinned at 2x, printed by the test that carries it so a
-Vrun records the current value beside the pin.The tests and what each pins
tests/test_fft_fixed.cpp(typed over{Q15, Q31} x {fixed, block_floating}unless noted):FixedExponentIsTheStatedConstantscaling::fixed: e ==fixed_scaling_exponent(n)== log2 n + pre-shift (Q15: log2 n, Q31: log2 n + 1) from all four entry points, both directions, 4 <= n <= 65536, also on silence;static_asserted constexpr/noexceptBfpExponentIsWithinRangescaling::block_floating: 0 <= e <= the constant, both directions, on silence, one LSB, noise at three levels, full-scale DC/INT_MIN and every adversarial patternFixedForwardScaleIsExactlyXOverNfft_fixed_q31.FixedForwardScaleIsExactlyXOverTwoNFixedInverseCarriesTheSameExponentSaturationFreeWorstCaseDoesNotWrapSilenceIsSilenceRoundTripReconstructsInputPerPolicyOutOfPlaceIsCopyThenInPlaceforward()/inverse()bit-identical to the in-place calls (aliased too), same e; no 2/N on the fixed-point inverseBfpMatchesFixedAfterShiftBfpAtTheFullExponentIsBitIdenticalToFixedRoundingBiasOnNegatedInputIsBoundedfft_fixed_profiles.Q15AndQ31AgreeToTheQ15FloorNoiseFloorTracksWelchModelfft_fixed_tables.TwiddleTableIsWithinHalfLsbfft_fixed_tables.TwiddleTableChecksumIsPinneddetail::table_checksum) of both tables at N = 256 / 512 / 2048tests/test_fft.cpp:real_fft_testnow runsSizeAndBinCount,RoundTripReproducesInput,RoundTripInPlaceAndAliased,ImpulseHasFlatSpectrum,DcAndNyquistPacking,SignConventionIsPlusI,ParsevalEnergyConservationonfloat, double, int16_t, int32_tthrough a per-typeprofile(input conversion, forward scale from the returned exponent, tolerance);Q15TracksDouble/Q31TracksDoublebesideFloatTracksDouble. The float/double comparisons are numerically what they were (same signals, same tolerances).tests/test_fft_oracle.cpp:profile<int16_t>/profile<int32_t>(full scale 1 - 2^-15 / 1 - 2^-31, scales 2^-e, tolerance derived fromfft_arith.h's rounding count: 4q per kernel stage + 4q post-pass + q/2 pre-shift + half an I/O LSB each way, doubled), appended tooracle_types: the closed forms to 65536 and the double-double DFT to 256 pass on all four profiles.tests/test_fft_rt.cpp: typed over the six FFT classes (real_fft32,real_fft,real_fft_q15,real_fft_q31,real_fft_q15_bfp,real_fft_q31_bfp):static_assert(noexcept)on every transform, the operator-new guard on all four transforms (first call and steady state), copy / unwarmed copy / copy-assignment bit-identity including the returned exponents.Measured numbers (x86-64 Linux, GCC 13.3.0 and clang 18.1.3 -O3 agree; kernel a55a14f; 2026-09-18; pins at 2x)
Floors (from the printed table): Q15 fixed -101.0 dBFS/component at every N and level (the narrow's 1/sqrt(12) LSB; per-bin SNR on full-scale noise 69.1 / 66.5 / 60.2 dB at N = 256 / 512 / 2048, 29.7 / 26.6 / 20.3 dB at -40 dBFS); Q31 fixed -189.7 .. -190.2 dBFS (model -191.3 .. -191.5: the ratio 1.3-1.7 is the round-half-up bias the kernel documents; SNR 152 / 149 / 143 dB); Q15 bfp holds 84-91 dB SNR at 0 dBFS and 77-86 dB at -40 dBFS; Q31 bfp 157-161 dB at 0 dBFS, 135-136 dB at -60 dBFS (e = 0). The on-bin tone sits far below the white model (ratio 0.001-0.6) because most butterflies then run on exact zeros, which round to nothing.
test_fft.cpppins: Q15 / Q31 contract tolerance 2 LSB (measured 1.0 / 1.0), round trip 1.02 / 6.7 reconstructed LSB (0.51 / 3.34), Parseval 1.43e-5 / 3.7e-9 (7.15e-6 / 1.83e-9),Q15TracksDouble1.4e-3 (6.949e-4, the predicted narrowing 6.9e-4),Q31TracksDouble1.1e-7 (5.458e-8).Table checksums (this host; the other CI hosts are the first cross-libm check): N = 256 twiddles
95f5c68afe494835post-pass66c84a75861eafb6; N = 5126df6ff99a3ed3c85/4b1374200abed27c; N = 2048e42c528f3ae88b45/a0f40e80bbf4efb9. Max |w_q - w| 0.4974 LSB.Verification
Built and ran
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DTAP_DSP_WERROR=ON -DTAP_DSP_BUILD_CAPI=ON -DTAP_DSP_BUILD_BENCH=ONwith g++ 13.3 and clang++ 18.1 on this branch (kernel a55a14f underneath): 397 / 397 ctest passes on both. clang-tidy-18 asstyle.ymlruns it: clean on the five changed files. clang-format via the pre-commit hook. The fixed-profile selection (82 tests) runs in 0.29 s on the host; nothing is added toMAIN_FILTER, so the whole battery runs on the four QEMU legs, which are the first real gate for the on-target run and for the macOS / Windows libm checksums (not runnable here). Fixed seeds, no wall clock, no filesystem, no<random>in new code (test_fft.cppkeeps its existing mt19937 for the unchanged float/double signals).Kernel findings
No kernel defect: every test passes against a55a14f as measured. Two things for the kernel PR (#27), posted there as well:
fft.h,fixed_point.h,tables.h) cite test names that do not exist in this battery (ForwardScaleIsExactlyXOverN,FixedExponentIsTheConstant,BlockFloatingExponentIsBounded,WorstCaseGrowthDoesNotSaturate,NoiseFloorTracksWelch,TransformsDoNotAllocate,CopiesAreBitIdentical,TwiddleQuantizationIsHalfAnLsb); the names in the table above are the ones to cite.docs/fft-fixed-point.md§3 quotes the worst-case sweep at 2.3 / 5.6 LSB (Q31 forward / inverse); this sweep, with more patterns and sizes and both policies, measures 4.25 LSB (Q31 fixed, inverse of a full-scale constant at N = 2048, index 0) and 16.0 LSB (Q31 bfp, inverse of binary noise at N = 1024, index 0), and 62 LSB of F(x)+F(-x) asymmetry at index 1 under Q31 bfp. All on the DC/Nyquist path the note calls the honest limit; pinned as measured, and worth quoting in the note.Nothing in the API contract proved wrong in practice. One nuance: "e == the constant implies bit-identical to fixed" is not implied by the per-stage clamp alone, but it holds on every one of the 176 patterns that reach the constant here.
Notes for the reviewer
claude/wave2-stage3b-kernel, notmain; rebased onto its head each time it moves. Onlytests/changes.test_fft_oracle.cpptolerance for the fixed profiles is derived (a worst-case rounding count), not fitted; the measured pins live intest_fft.cpp/test_fft_fixed.cpp.🤖 Generated with Claude Code
https://claude.ai/code/session_019ZPTzNxo5Fe4EtpXXKf7Sy
Generated by Claude Code