Skip to content

DCT is not orthonormal, and the class name is misspelled #251

Description

@gabrielfrasantos

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 DiscreteConsineTransformDiscreteCosineTransform (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions