Skip to content

Commit 0c04c8d

Browse files
Merge branch 'main' into feature/add-savitzky-golay-filter
2 parents 3414239 + cab893e commit 0c04c8d

12 files changed

Lines changed: 429 additions & 168 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Refer to the documentation to quickly integrate and utilize the library's signal
2626
| [Neural Network](doc/neural_network/README.md) | Layers, activations, losses, model |
2727
| [Optimization](doc/optimization/README.md) | Gradient Descent |
2828
| [Regularization](doc/regularization/README.md) | L1 (Lasso), L2 (Ridge) |
29-
| [Math](doc/math/README.md) | Quaternion |
29+
| [Math](doc/math/README.md) | CORDIC, Quaternion |
3030
| [Solvers](doc/solvers/README.md) | Gaussian Elimination, Levinson-Durbin, Durand-Kerner, Cholesky, DARE |
3131
| [Performance Optimization](doc/performance-optimization/README.md) | Compiler optimizations, SIMD |
3232

ROADMAP.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ Difficulty legend:
2929
|----|------------------------------------------------------|---------------------------|------------|
3030
| 15 | Biquad / Second-Order-Section cascade | `filters/passive` | ★★★☆☆ |
3131
| 20 | Integral / servo state feedback (LQI) | `controllers` | ★★★☆☆ |
32-
| 23 | CORDIC | `math` | ★★★☆☆ |
3332
| 24 | Runge-Kutta ODE integrators (RK4 + Dormand-Prince) | `solvers` | ★★★☆☆ |
3433
| 25 | Real-input FFT (RFFT) | `analysis` | ★★★☆☆ |
3534
| 26 | Controllability / Observability matrices & Gramians | `control_analysis` | ★★★☆☆ |

doc/math/Cordic.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# CORDIC
2+
3+
## Overview & Motivation
4+
5+
Trigonometric functions, magnitudes, and vector rotations are fundamental operations in motor
6+
control, radar processing, and navigation. On microcontrollers without a floating-point unit or
7+
hardware multiplier, library implementations of `sin`, `cos`, `atan2`, and `hypot` require
8+
expensive software-emulated multiplications. This limits their use in hard real-time loops where
9+
deterministic execution time is mandatory.
10+
11+
CORDIC (COordinate Rotation DIgital Computer) solves this by expressing any planar rotation as a
12+
sum of progressively smaller elementary rotations, each of which requires only a bit-shift and an
13+
addition. The result is a trig engine that runs on shift-add hardware with a fixed, data-independent
14+
cycle count — exactly what a deterministic real-time loop demands.
15+
16+
## Mathematical Theory
17+
18+
### Elementary Rotations
19+
20+
A rotation by angle $\theta$ in two dimensions transforms a vector $(x, y)$ to
21+
22+
$$x' = x\cos\theta - y\sin\theta, \quad y' = y\cos\theta + x\sin\theta.$$
23+
24+
Factoring out $\cos\theta$ gives
25+
26+
$$x' = \cos\theta\,(x - y\tan\theta), \quad y' = \cos\theta\,(y + x\tan\theta).$$
27+
28+
When $\tan\theta_i = \pm 2^{-i}$, the multiplication by $\tan\theta_i$ becomes a right-shift by $i$
29+
bits. The angles $\theta_i = \arctan(2^{-i})$ form the **CORDIC angle table**.
30+
31+
### Rotation Mode (sin/cos)
32+
33+
Starting from $(x_0, y_0, z_0) = (K, 0, \theta)$, each iteration steers the residual angle $z$
34+
toward zero:
35+
36+
$$x_{i+1} = x_i - \sigma_i \, 2^{-i} y_i$$
37+
$$y_{i+1} = y_i + \sigma_i \, 2^{-i} x_i$$
38+
$$z_{i+1} = z_i - \sigma_i \, \theta_i$$
39+
40+
where $\sigma_i = \text{sign}(z_i)$. After $N$ iterations, $x_N \approx \cos\theta$ and
41+
$y_N \approx \sin\theta$.
42+
43+
### Vectoring Mode (atan2/magnitude)
44+
45+
Starting from $(x_0, y_0, z_0) = (x, y, 0)$, each iteration steers $y$ toward zero:
46+
47+
$$\sigma_i = -\text{sign}(y_i)$$
48+
49+
After $N$ iterations, $z_N \approx \arctan(y/x)$ and $x_N \approx \|(x, y)\| / K$.
50+
51+
### CORDIC Gain
52+
53+
Each elementary rotation stretches the vector length by $\sqrt{1 + 2^{-2i}}$. The accumulated
54+
gain over $N$ iterations is
55+
56+
$$A_N = \prod_{i=0}^{N-1} \sqrt{1 + 2^{-2i}}.$$
57+
58+
The constant $K = 1/A_N \approx 0.6073$ compensates for this growth. In rotation mode the initial
59+
$x$ is pre-scaled by $K$; in vectoring mode the final $x$ is multiplied by $K$.
60+
61+
### Convergence Domain
62+
63+
The convergence domain is $|z| \leq \sum_{i=0}^{N-1} \arctan(2^{-i})$. For $N = 16$ this exceeds
64+
$\pi/2$, so inputs outside $[-\pi/2, \pi/2]$ must be range-reduced by shifting the angle by $\pm\pi$
65+
and inverting the output signs. Vectoring mode uses quadrant detection on the signs of $x$ and $y$
66+
to handle the full $[-\pi, \pi]$ range.
67+
68+
## Complexity Analysis
69+
70+
| Metric | Value |
71+
|-------------|-------------------------------------------------------|
72+
| Time | $O(N)$ — exactly $N$ shift-add steps per call |
73+
| Space | $O(N)$ — angle table in ROM; $O(1)$ working registers |
74+
| Cycle count | Fixed, data-independent — no branch on input value |
75+
76+
One additional bit of precision is gained per iteration. $N = 16$ yields approximately 16-bit
77+
accuracy; $N = 20$ reaches the limits of single-precision float.
78+
79+
## Step-by-Step Walkthrough
80+
81+
Compute $\sin(\pi/6) = 0.5$ with $N = 4$ for brevity (gain $K_4 \approx 0.6352$).
82+
83+
| $i$ | $\theta_i$ | $\sigma_i$ | $x_i$ | $y_i$ | $z_i$ |
84+
|-----|------------|------------|--------|--------|---------|
85+
|||| 0.6352 | 0.0000 | 0.5236 |
86+
| 0 | 0.7854 | +1 | 0.6352 | 0.6352 | −0.2618 |
87+
| 1 | 0.4636 | −1 | 0.7940 | 0.3176 | 0.2018 |
88+
| 2 | 0.2450 | +1 | 0.7147 | 0.5122 | −0.0432 |
89+
| 3 | 0.1244 | −1 | 0.8425 | 0.4248 | 0.0812 |
90+
91+
After iteration 3: $y_4 \approx 0.43$, improving toward 0.5 as $N$ grows.
92+
93+
## Pitfalls & Edge Cases
94+
95+
The input to rotation mode must lie within the convergence domain after range reduction. Angles
96+
at exactly $\pm\pi/2$ sit at the edge of the domain and may accumulate an extra half-ulp error.
97+
98+
In vectoring mode, $(x, y) = (0, 0)$ is degenerate; by convention the angle is returned as zero
99+
rather than causing a division-by-zero or NaN.
100+
101+
The shift $2^{-i}$ eventually underflows in floating-point for large $i$; iterations beyond
102+
$\lfloor -\log_2(\epsilon) \rfloor$ contribute nothing and can be capped without loss of accuracy.
103+
104+
## Variants & Generalizations
105+
106+
**Hyperbolic CORDIC** replaces the elementary angle table with $\tanh^{-1}(2^{-i})$ and handles
107+
`sinh`, `cosh`, `exp`, and `ln`.
108+
109+
**Linear CORDIC** uses shifts alone (no rotation) to implement multiply and divide.
110+
111+
**Double-rotation trick** repeats certain iterations to extend the convergence domain to $(-\pi, \pi]$
112+
without a range-reduction step.
113+
114+
## Applications
115+
116+
- Field-oriented motor control: Park/Clarke transforms require `sin`/`cos` at carrier frequency.
117+
- Radar and sonar: Cartesian-to-polar conversion of sample streams.
118+
- Navigation: continuous `atan2` for heading on heading-constrained MCUs.
119+
- Audio synthesis: wavetable-free sine generation on FPU-less targets.
120+
121+
## Connections to Other Algorithms
122+
123+
`TrigonometricFunctions` provides a table-lookup alternative with lower iteration count but higher
124+
ROM usage for the same precision. `Quaternion` consumes CORDIC-generated `sin`/`cos` for axis-angle
125+
conversions. On targets with an FPU the standard library usually outperforms CORDIC; the advantage
126+
is exclusive to multiply-poor hardware.
127+
128+
## References & Further Reading
129+
130+
- J. E. Volder, "The CORDIC Trigonometric Computing Technique," *IRE Transactions on Electronic Computers*, EC-8(3), pp. 330–334, 1959.
131+
- R. Andraka, "A survey of CORDIC algorithms for FPGA-based computers," *Proc. ACM/SIGDA FPGA*, 1998, pp. 191–200.
132+
- J. S. Walther, "A unified algorithm for elementary functions," *AFIPS Spring Joint Computer Conference*, 1971.

doc/math/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ Core mathematical primitives for numerical computation.
66

77
| Algorithm | Description |
88
|-----------------------------|-----------------------------------------------------------------------------------------------|
9+
| [CORDIC](Cordic.md) | Iterative shift-add engine for sin/cos, atan2, magnitude, and vector rotation — no multiplier |
910
| [Quaternion](Quaternion.md) | Unit-quaternion rotation type: Hamilton product, SLERP, rotation-matrix and Euler conversions |

numerical/math/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ target_link_libraries(numerical.math ${NUMERICAL_VISIBILITY}
1212
target_sources(numerical.math PRIVATE
1313
AdvancedFunctions.hpp
1414
ComplexNumber.hpp
15+
Cordic.hpp
1516
Geometry3D.hpp
1617
HyperbolicFunctions.hpp
1718
LinearTimeInvariant.hpp
@@ -28,6 +29,7 @@ target_sources(numerical.math PRIVATE
2829

2930
numerical_add_coverage_sources(numerical.math
3031
ComplexNumber.cpp
32+
Cordic.cpp
3133
LinearTimeInvariant.cpp
3234
Matrix.cpp
3335
QNumber.cpp

numerical/math/Cordic.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "numerical/math/Cordic.hpp"
2+
3+
namespace math
4+
{
5+
template class Cordic<float, 16>;
6+
template class Cordic<float, 8>;
7+
}

numerical/math/Cordic.hpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#pragma once
2+
3+
#if defined(__GNUC__) || defined(__clang__)
4+
#pragma GCC optimize("O3", "fast-math")
5+
#endif
6+
7+
#include "numerical/math/CompilerOptimizations.hpp"
8+
#include <array>
9+
#include <cmath>
10+
#include <cstddef>
11+
#include <numbers>
12+
#include <type_traits>
13+
14+
namespace math
15+
{
16+
template<typename T, std::size_t Iterations = 16>
17+
class Cordic
18+
{
19+
static_assert(std::is_floating_point_v<T>, "Cordic supports floating-point types only");
20+
21+
public:
22+
struct SinCos
23+
{
24+
T sin;
25+
T cos;
26+
};
27+
28+
struct PolarResult
29+
{
30+
T magnitude;
31+
T angle;
32+
};
33+
34+
OPTIMIZE_FOR_SPEED SinCos SineCosine(T angleRadians) const;
35+
T Arctangent2(T y, T x) const;
36+
T Magnitude(T y, T x) const;
37+
std::array<T, 2> Rotate(std::array<T, 2> v, T angle) const;
38+
39+
private:
40+
struct VectoringResult
41+
{
42+
T x;
43+
T angle;
44+
};
45+
46+
VectoringResult VectoringMode(T xv, T yv) const
47+
{
48+
T z{ T(0) };
49+
for (std::size_t i = 0; i < Iterations; ++i)
50+
{
51+
T d{ (yv >= T(0)) ? T(-1) : T(1) };
52+
T pow2i{ T(1) / static_cast<T>(std::size_t(1) << i) };
53+
T xNew{ xv - d * yv * pow2i };
54+
T yNew{ yv + d * xv * pow2i };
55+
z -= d * atanTable[i];
56+
xv = xNew;
57+
yv = yNew;
58+
}
59+
return VectoringResult{ xv, z };
60+
}
61+
62+
static std::array<T, Iterations> BuildAtanTable()
63+
{
64+
std::array<T, Iterations> table{};
65+
for (std::size_t i = 0; i < Iterations; ++i)
66+
table[i] = static_cast<T>(std::atan(std::pow(T(2), -static_cast<T>(i))));
67+
return table;
68+
}
69+
70+
static T ComputeK()
71+
{
72+
T k{ T(1) };
73+
for (std::size_t i = 0; i < Iterations; ++i)
74+
k *= static_cast<T>(std::cos(std::atan(std::pow(T(2), -static_cast<T>(i)))));
75+
return k;
76+
}
77+
78+
static inline const std::array<T, Iterations> atanTable{ BuildAtanTable() };
79+
static inline const T K{ ComputeK() };
80+
};
81+
82+
template<typename T, std::size_t Iterations>
83+
OPTIMIZE_FOR_SPEED typename Cordic<T, Iterations>::SinCos Cordic<T, Iterations>::SineCosine(T angleRadians) const
84+
{
85+
const T pi{ std::numbers::pi_v<T> };
86+
const T halfPi{ pi / T(2) };
87+
88+
T angle{ angleRadians };
89+
T sinSign{ T(1) };
90+
T cosSign{ T(1) };
91+
92+
if (angle > halfPi)
93+
{
94+
angle -= pi;
95+
sinSign = T(-1);
96+
cosSign = T(-1);
97+
}
98+
else if (angle < -halfPi)
99+
{
100+
angle += pi;
101+
sinSign = T(-1);
102+
cosSign = T(-1);
103+
}
104+
105+
T x{ K };
106+
T y{ T(0) };
107+
T z{ angle };
108+
109+
for (std::size_t i = 0; i < Iterations; ++i)
110+
{
111+
T d{ (z >= T(0)) ? T(1) : T(-1) };
112+
T pow2i{ T(1) / static_cast<T>(std::size_t(1) << i) };
113+
T xNew{ x - d * y * pow2i };
114+
T yNew{ y + d * x * pow2i };
115+
z -= d * atanTable[i];
116+
x = xNew;
117+
y = yNew;
118+
}
119+
120+
return SinCos{ sinSign * y, cosSign * x };
121+
}
122+
123+
template<typename T, std::size_t Iterations>
124+
T Cordic<T, Iterations>::Arctangent2(T y, T x) const
125+
{
126+
const T pi{ std::numbers::pi_v<T> };
127+
128+
T quadrantOffset{ T(0) };
129+
T xv{ x };
130+
T yv{ y };
131+
132+
if (xv < T(0) && yv >= T(0))
133+
{
134+
xv = -x;
135+
yv = -y;
136+
quadrantOffset = pi;
137+
}
138+
else if (xv < T(0) && yv < T(0))
139+
{
140+
xv = -x;
141+
yv = -y;
142+
quadrantOffset = -pi;
143+
}
144+
145+
auto res = VectoringMode(xv, yv);
146+
return res.angle + quadrantOffset;
147+
}
148+
149+
template<typename T, std::size_t Iterations>
150+
T Cordic<T, Iterations>::Magnitude(T y, T x) const
151+
{
152+
T xv{ (x < T(0)) ? -x : x };
153+
T yv{ (y < T(0)) ? -y : y };
154+
auto res = VectoringMode(xv, yv);
155+
return K * res.x;
156+
}
157+
158+
template<typename T, std::size_t Iterations>
159+
std::array<T, 2> Cordic<T, Iterations>::Rotate(std::array<T, 2> v, T angle) const
160+
{
161+
auto sc = SineCosine(angle);
162+
return std::array<T, 2>{ v[0] * sc.cos - v[1] * sc.sin, v[0] * sc.sin + v[1] * sc.cos };
163+
}
164+
165+
#ifdef NUMERICAL_TOOLBOX_COVERAGE_BUILD
166+
extern template class Cordic<float, 16>;
167+
extern template class Cordic<float, 8>;
168+
#endif
169+
}

numerical/math/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ target_link_libraries(numerical.math_test PUBLIC
99

1010
target_sources(numerical.math_test PRIVATE
1111
TestComplexNumber.cpp
12+
TestCordic.cpp
1213
TestLinearTimeInvariant.cpp
1314
TestQNumber.cpp
1415
TestMatrix.cpp

0 commit comments

Comments
 (0)