Enhancement-241 fixed an amplitude normalization that divided by the ZERO-PADDED
transform size instead of the number of input samples -- in the `fft` COMMAND
(frontend/com_fft.c). The identical mistake survived in maths/cmaths/cmath4.c, the
vector-expression function reached by `let F = fft(v)`: a separate implementation of
the same computation.
Found by continuing the oracle campaign that produced E-241, and located with E-241's
own discriminator, the DC bin. One signal, 4001 samples padded to 4096, DC offset 2.0:
fft s ; the COMMAND -> mag(s)[0] = 2.000000 correct
let F = fft(s) ; the FUNCTION -> mag(F)[0] = 1.953613 = 2.0 * 4001/4096
X[0] is the sum of the samples, D*length for a DC offset D, so dividing by the padded N
reads back D*length/N. As E-241 put it: a DC value cannot depend on how many samples
were taken.
This is a contradiction, not a matter of convention. cx_fft holds TWO complete
implementations -- one for complex input, one for real -- and each has an FFTW branch
and a Green's radix-2 branch. In BOTH, the FFTW branch already used the input length
while Green's used the padded size:
real branch FFTW: scale = ((double)length)/2.0 Green: ((double)N)/2 <- wrong
complex branch FFTW: scale = (double) fpts Green: (double) N <- wrong
the correct version sitting a few lines from the wrong one inside the same function --
the same shape as `avg` disagreeing with `integ` in E-302. HAVE_LIBFFTW3 is undefined in
this build, so Green's is the live path and the defect was reachable.
oracle before after closed form
real-input, DC bin 1.953613 2.000000 2.0
real-input, ifft(fft(x)) 2.3e-02 1.1e-16 0
complex-input, bin 0 0.9766780 0.9998683 0.9998683
The round trip is an INDEPENDENT confirmation: nothing here touches ifft, so its going
from 2.3% error to machine precision is evidence from a direction the fix did not aim
at -- the pair only inverts when the forward normalization is right. cx_ifft was
audited and deliberately left alone for that reason.
Every caller of the Green's radix-2 kernel was audited, not only the one that failed:
com_fft.c's fft (x2) and spec/PSD (x2) are correct from E-241; cx_ifft is correct;
trannoise/1-f-code.c cannot pad at all, since n_pts is grown to 2^n_exp by construction;
and fft/ifft are the only transform functions in the expression table, so there is no
spec twin to miss.
Verification: examples/fftexpr_examples/verify_fftexpr.py -- 6 checks under both
solvers, all against closed form (DC bin on both paths, round trip padded and unpadded,
complex-input bin 0 against the analytic mean of an RC response). It scores 3/6 on the
pre-fix binary, so it is a real regression guard. E-241's own suite (fftnorm_examples)
and ifftreal_examples pass unchanged. Full sweep 241/241 OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>