Severity: high
Domain: analysis
Status: VERIFIED — read + hand-traced against a210d34 on 2026-08-10
Suggested labels: bug, numerical-correctness, analysis, api
Summary
The forward DCT-II scales non-DC coefficients by 2/√N instead of the orthonormal √(2/N) — a
factor of √2 too large. Parseval's identity does not hold, and the transform does not match the
scaling documented in doc/analysis/DiscreteCosineTransform.md.
Separately, the public class name is misspelled DiscreteConsineTransform ("Consine").
Location
numerical/analysis/DiscreteCosineTransform.hpp
Evidence
output[0] = QNumberType(math::ToFloat(fftResult[0].Real()) / math::Sqrt(static_cast<float>(Length)));
for (std::size_t k = 1; k < Length; ++k)
{
float angle = -static_cast<float>(k) * std::numbers::pi_v<float> / (2.0f * Length);
float scale = 2.0f / math::Sqrt(static_cast<float>(Length)); // should be sqrt(2/N)
...
}
The DC term is correct (√(1/N)); the non-DC scale is not.
implemented: 2/√N
orthonormal: √(2/N) = √2/√N
ratio: 2/√2 = √2 ≈ 1.414
Trace
N = 8, unit impulse at n = 0. Coefficient energy should be 1:
Σ c_k² = (1/N)·1 + Σ_{k=1}^{N-1} (4/N)·cos²(πk/2N)
= 1/N + (4/N)·(N-1)/2
= 2 − 1/N = 1.875 (should be 1)
Why tests do not catch it
Inverse() applies the reciprocal scale, so any test of the form
Inverse(Forward(x)) == x passes. The error is only visible to a test that checks a single
coefficient against an analytic reference, or that asserts Parseval.
Suggested fix
float scale = math::Sqrt(2.0f / static_cast<float>(Length));
Adjust Inverse() symmetrically. Add a Parseval test and an analytic single-coefficient test.
Rename DiscreteConsineTransform → DiscreteCosineTransform (breaking change; the file is already
named correctly).
Notes
The documentation describes an unnormalised DCT, which matches neither the current code nor the
orthonormal convention — decide on one contract and align all three.
Severity: high
Domain: analysis
Status: VERIFIED — read + hand-traced against
a210d34on 2026-08-10Suggested labels:
bug,numerical-correctness,analysis,apiSummary
The forward DCT-II scales non-DC coefficients by
2/√Ninstead of the orthonormal√(2/N)— afactor of
√2too large. Parseval's identity does not hold, and the transform does not match thescaling documented in doc/analysis/DiscreteCosineTransform.md.
Separately, the public class name is misspelled
DiscreteConsineTransform("Consine").Location
numerical/analysis/DiscreteCosineTransform.hpp
Evidence
The DC term is correct (
√(1/N)); the non-DC scale is not.Trace
N = 8, unit impulse atn = 0. Coefficient energy should be1:Why tests do not catch it
Inverse()applies the reciprocal scale, so any test of the formInverse(Forward(x)) == xpasses. The error is only visible to a test that checks a singlecoefficient against an analytic reference, or that asserts Parseval.
Suggested fix
Adjust
Inverse()symmetrically. Add a Parseval test and an analytic single-coefficient test.Rename
DiscreteConsineTransform→DiscreteCosineTransform(breaking change; the file is alreadynamed correctly).
Notes
The documentation describes an unnormalised DCT, which matches neither the current code nor the
orthonormal convention — decide on one contract and align all three.