Skip to content

Commit adbd6e7

Browse files
singalsulgirdwood
authored andcommitted
Math: Add function sofm_atan2_32b() to SOF math library
This patch adds the four quadrant arctan function version. It is useful for fast computation of phase angle -pi to +pi in radians from a complex number (real, imaginary) value. The approximation uses a 9th order polynomial that is implemented with Horner's rule evaluation. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent c472ca3 commit adbd6e7

4 files changed

Lines changed: 142 additions & 0 deletions

File tree

src/include/sof/math/trig.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,4 +383,18 @@ static inline int16_t acos_fixed_16b(int32_t cdc_acos_th)
383383
return sat_int16(Q_SHIFT_RND(th_acos_fxp, 29, 13));
384384
}
385385

386+
/**
387+
* sofm_atan2_32b() - Four-quadrant arctangent using degree-9 Remez minimax polynomial
388+
* @param y Imaginary component (sine) in Q1.31 format.
389+
* @param x Real component (cosine) in Q1.31 format.
390+
* @return Angle in Q3.29 radians, range [-pi, +pi].
391+
*
392+
* Uses the Horner-form polynomial:
393+
* atan(z) = z * (C0 + z^2 * (C1 + z^2 * (C2 + z^2 * (C3 + z^2 * C4))))
394+
*
395+
* with Remez minimax coefficients on [0, 1].
396+
* Maximum error ~0.001 degrees (1.94e-5 radians).
397+
*/
398+
int32_t sofm_atan2_32b(int32_t y, int32_t x);
399+
386400
#endif /* __SOF_MATH_TRIG_H__ */

src/math/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ if(CONFIG_CORDIC_FIXED)
1111
list(APPEND base_files trig.c)
1212
endif()
1313

14+
if(CONFIG_MATH_ATAN2)
15+
list(APPEND base_files atan2.c)
16+
endif()
17+
1418
if(CONFIG_MATH_LUT_SINE_FIXED)
1519
list(APPEND base_files lut_trig.c)
1620
endif()

src/math/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ config CORDIC_FIXED
99
Select this to enable sin(), cos(), asin(), acos(),
1010
and cexp() functions as 16 bit and 32 bit versions.
1111

12+
config MATH_ATAN2
13+
bool "Four-quadrant arctangent function"
14+
default n
15+
help
16+
Select this to enable sofm_atan2_32b(y, x) function. It computes
17+
the four-quadrant arctangent using a polynomial approximation.
18+
Input arguments y and x are Q1.31 format and the output angle
19+
is Q3.29 format with range [-pi, pi]. The maximum approximation
20+
error is ~0.001 degrees.
21+
1222
config MATH_LUT_SINE_FIXED
1323
bool "Lookup table based sine function"
1424
default n

src/math/atan2.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2026 Intel Corporation.
4+
//
5+
// Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
6+
7+
/**
8+
* \file math/atan2.c
9+
* \brief Four-quadrant arctangent function using polynomial approximation
10+
*/
11+
12+
#include <rtos/symbol.h>
13+
#include <sof/audio/format.h>
14+
#include <sof/math/trig.h>
15+
#include <stdint.h>
16+
17+
/*
18+
* Degree-9 Remez minimax polynomial for atan(z) in first octant, z in [0, 1]:
19+
*
20+
* atan(z) = z * (C0 + z^2 * (C1 + z^2 * (C2 + z^2 * (C3 + z^2 * C4))))
21+
*
22+
* Coefficients are in Q3.29 format, computed via Remez exchange algorithm
23+
* to minimize the maximum approximation error over [0, 1].
24+
* Maximum approximation error is ~0.0011 degrees (1.94e-5 radians).
25+
*/
26+
#define SOFM_ATAN2_C0 536890772 /* Q3.29, +1.000036992319 */
27+
#define SOFM_ATAN2_C1 -178298711 /* Q3.29, -0.332107229582 */
28+
#define SOFM_ATAN2_C2 99794340 /* Q3.29, +0.185881443393 */
29+
#define SOFM_ATAN2_C3 -49499604 /* Q3.29, -0.092200197922 */
30+
#define SOFM_ATAN2_C4 12781063 /* Q3.29, +0.023806584771 */
31+
32+
/**
33+
* sofm_atan2_32b() - Compute four-quadrant arctangent of y/x
34+
* @param y Imaginary component in Q1.31 format
35+
* @param x Real component in Q1.31 format
36+
* @return Angle in Q3.29 radians, range [-pi, +pi]
37+
*
38+
* Uses degree-9 Remez minimax polynomial for the core atan(z) computation
39+
* where z = min(|y|,|x|) / max(|y|,|x|) is reduced to [0, 1] range.
40+
* Octant and quadrant corrections extend the result to full [-pi, +pi].
41+
*/
42+
int32_t sofm_atan2_32b(int32_t y, int32_t x)
43+
{
44+
int32_t abs_x;
45+
int32_t abs_y;
46+
int32_t num;
47+
int32_t den;
48+
int32_t z;
49+
int32_t z2;
50+
int32_t acc;
51+
int32_t angle;
52+
int swap;
53+
54+
/* Return zero for origin */
55+
if (x == 0 && y == 0)
56+
return 0;
57+
58+
/* Take absolute values, handle INT32_MIN overflow */
59+
abs_x = (x > 0) ? x : ((x == INT32_MIN) ? INT32_MAX : -x);
60+
abs_y = (y > 0) ? y : ((y == INT32_MIN) ? INT32_MAX : -y);
61+
62+
/* Reduce to first octant: z = min/max ensures z in [0, 1] */
63+
if (abs_y <= abs_x) {
64+
num = abs_y;
65+
den = abs_x;
66+
swap = 0;
67+
} else {
68+
num = abs_x;
69+
den = abs_y;
70+
swap = 1;
71+
}
72+
73+
/* z = num / den in Q1.31, den is always > 0 here */
74+
z = sat_int32(((int64_t)num << 31) / den);
75+
76+
/*
77+
* Horner evaluation of degree-9 Remez minimax polynomial:
78+
* atan(z) = z * (C0 + z^2 * (C1 + z^2 * (C2 + z^2 * (C3 + z^2 * C4))))
79+
*
80+
* z is Q1.31, coefficients are Q3.29.
81+
* Multiplications: Q1.31 * Q3.29 = Q4.60, >> 31 -> Q3.29.
82+
*/
83+
84+
/* z^2: Q1.31 * Q1.31 = Q2.62, >> 31 -> Q1.31 */
85+
z2 = (int32_t)(((int64_t)z * z) >> 31);
86+
87+
/* Innermost: C3 + z^2 * C4 */
88+
acc = (int32_t)(((int64_t)z2 * SOFM_ATAN2_C4) >> 31) + SOFM_ATAN2_C3;
89+
90+
/* C2 + z^2 * (C3 + z^2 * C4) */
91+
acc = (int32_t)(((int64_t)z2 * acc) >> 31) + SOFM_ATAN2_C2;
92+
93+
/* C1 + z^2 * (C2 + z^2 * (C3 + z^2 * C4)) */
94+
acc = (int32_t)(((int64_t)z2 * acc) >> 31) + SOFM_ATAN2_C1;
95+
96+
/* C0 + z^2 * (C1 + z^2 * (C2 + z^2 * (C3 + z^2 * C4))) */
97+
acc = (int32_t)(((int64_t)z2 * acc) >> 31) + SOFM_ATAN2_C0;
98+
99+
/* angle = z * acc: Q1.31 * Q3.29 = Q4.60, >> 31 -> Q3.29 */
100+
angle = (int32_t)(((int64_t)z * acc) >> 31);
101+
102+
/* If |y| > |x|, use identity: atan(y/x) = pi/2 - atan(x/y) */
103+
if (swap)
104+
angle = PI_DIV2_Q3_29 - angle;
105+
106+
/* Map to correct quadrant based on signs of x and y */
107+
if (x < 0)
108+
angle = (y >= 0) ? PI_Q3_29 - angle : -(PI_Q3_29 - angle);
109+
else if (y < 0)
110+
angle = -angle;
111+
112+
return angle;
113+
}
114+
EXPORT_SYMBOL(sofm_atan2_32b);

0 commit comments

Comments
 (0)