#![allow(clippy::unreadable_literal)]
use core::f64::consts::PI;
#[inline]
pub const fn const_cos(x: f64) -> f64 {
let x = reduce_angle(x);
let x2 = x * x;
let x4 = x2 * x2;
let x6 = x4 * x2;
let x8 = x4 * x4;
let x10 = x6 * x4;
let x12 = x6 * x6;
let x14 = x8 * x6;
1.0 - x2 / 2.0 + x4 / 24.0 - x6 / 720.0 + x8 / 40320.0 - x10 / 3628800.0 + x12 / 479001600.0
- x14 / 87178291200.0
}
#[inline]
pub const fn const_sin(x: f64) -> f64 {
let x = reduce_angle(x);
let x2 = x * x;
let x3 = x * x2;
let x5 = x3 * x2;
let x7 = x5 * x2;
let x9 = x7 * x2;
let x11 = x9 * x2;
let x13 = x11 * x2;
x - x3 / 6.0 + x5 / 120.0 - x7 / 5040.0 + x9 / 362880.0 - x11 / 39916800.0 + x13 / 6227020800.0
}
#[inline]
const fn reduce_angle(x: f64) -> f64 {
if x >= -PI && x <= PI {
return x;
}
let two_pi = 2.0 * PI;
let mut angle = x;
if angle > two_pi {
let n = (angle / two_pi) as i64;
angle = angle - (n as f64) * two_pi;
} else if angle < -two_pi {
let n = (-angle / two_pi) as i64;
angle = angle + (n as f64) * two_pi;
}
if angle > PI {
angle = angle - two_pi;
} else if angle < -PI {
angle = angle + two_pi;
}
angle
}
#[inline]
pub const fn twiddle_factor(k: usize, n: usize) -> (f64, f64) {
let angle = -2.0 * PI * (k as f64) / (n as f64);
(const_cos(angle), const_sin(angle))
}
#[inline]
#[allow(dead_code)]
pub const fn twiddle_factor_inv(k: usize, n: usize) -> (f64, f64) {
let angle = 2.0 * PI * (k as f64) / (n as f64);
(const_cos(angle), const_sin(angle))
}
#[allow(dead_code)]
pub struct TwiddleTable<const N: usize> {
pub cos: [f64; N],
pub sin: [f64; N],
}
#[allow(dead_code)]
impl<const N: usize> TwiddleTable<N> {
pub fn new() -> Self {
let mut cos = [0.0; N];
let mut sin = [0.0; N];
for k in 0..N {
let angle = -2.0 * PI * (k as f64) / (N as f64);
cos[k] = angle.cos();
sin[k] = angle.sin();
}
Self { cos, sin }
}
#[inline]
pub fn get(&self, k: usize) -> (f64, f64) {
(self.cos[k % N], self.sin[k % N])
}
}
impl<const N: usize> Default for TwiddleTable<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_const_cos_0() {
let c = const_cos(0.0);
assert!((c - 1.0).abs() < 1e-14);
}
#[test]
fn test_const_cos_pi_2() {
let c = const_cos(PI / 2.0);
assert!(c.abs() < 1e-10);
}
#[test]
fn test_const_cos_pi() {
let c = const_cos(PI);
assert!((c - (-1.0)).abs() < 1e-4, "cos(π) = {c}");
}
#[test]
fn test_const_sin_0() {
let s = const_sin(0.0);
assert!(s.abs() < 1e-14);
}
#[test]
fn test_const_sin_pi_2() {
let s = const_sin(PI / 2.0);
assert!((s - 1.0).abs() < 1e-8, "sin(π/2) = {s}");
}
#[test]
fn test_const_sin_pi() {
let s = const_sin(PI);
assert!(s.abs() < 1e-4, "sin(π) = {s}");
}
#[test]
fn test_const_sin_cos_identity() {
let angles = [
0.0,
PI / 6.0,
PI / 4.0,
PI / 3.0,
PI / 2.0,
PI,
3.0 * PI / 2.0,
];
for &angle in &angles {
let c = const_cos(angle);
let s = const_sin(angle);
let sum = c * c + s * s;
assert!(
(sum - 1.0).abs() < 1e-4,
"Identity failed for angle {angle}: {sum}"
);
}
}
#[test]
fn test_twiddle_factor_unity() {
let (c, s) = twiddle_factor(0, 8);
assert!((c - 1.0).abs() < 1e-10);
assert!(s.abs() < 1e-10);
}
#[test]
fn test_twiddle_factor_w8_1() {
let (c, s) = twiddle_factor(1, 8);
let sqrt2_2 = core::f64::consts::FRAC_1_SQRT_2;
assert!((c - sqrt2_2).abs() < 1e-10, "cos: {c} vs {sqrt2_2}");
assert!((s - (-sqrt2_2)).abs() < 1e-10, "sin: {} vs {}", s, -sqrt2_2);
}
#[test]
fn test_twiddle_factor_w4_1() {
let (c, s) = twiddle_factor(1, 4);
assert!(c.abs() < 1e-10, "cos should be 0: {c}");
assert!((s - (-1.0)).abs() < 1e-8, "sin should be -1: {s}");
}
#[test]
fn test_twiddle_table() {
let table = TwiddleTable::<8>::new();
for k in 0..8 {
let angle = -2.0 * PI * (k as f64) / 8.0;
let expected_cos = angle.cos();
let expected_sin = angle.sin();
assert!(
(table.cos[k] - expected_cos).abs() < 1e-14,
"cos mismatch at {}: {} vs {}",
k,
table.cos[k],
expected_cos
);
assert!(
(table.sin[k] - expected_sin).abs() < 1e-14,
"sin mismatch at {}: {} vs {}",
k,
table.sin[k],
expected_sin
);
}
}
}