|
| 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