use super::decimal_compute::{
ComputeStorage, DECIMAL_COMPUTE_DP,
decimal_compute_zero, decimal_compute_one, decimal_compute_halve,
decimal_compute_add, decimal_compute_sub, decimal_compute_mul,
decimal_compute_div, decimal_compute_div_int,
decimal_compute_is_zero, decimal_compute_is_negative, decimal_compute_neg,
decimal_compute_cmp, decimal_compute_abs,
};
use crate::fixed_point::domains::symbolic::rational::rational_number::OverflowDetected;
thread_local! {
static PI_CACHE: std::cell::RefCell<Option<ComputeStorage>> = const { std::cell::RefCell::new(None) };
}
pub fn pi_at_decimal_compute() -> Result<ComputeStorage, OverflowDetected> {
let cached: Option<ComputeStorage> = PI_CACHE.with(|c| c.borrow().clone());
if let Some(pi) = cached {
return Ok(pi);
}
let one = decimal_compute_one();
let five = super::decimal_compute::decimal_compute_from_int(5);
let two_thirty_nine = super::decimal_compute::decimal_compute_from_int(239);
let one_fifth = decimal_compute_div(one, five)?;
let one_239th = decimal_compute_div(one, two_thirty_nine)?;
let atan_fifth = atan_small_direct(one_fifth);
let atan_239 = atan_small_direct(one_239th);
let four_atan_fifth = decimal_compute_add(atan_fifth, decimal_compute_add(atan_fifth, decimal_compute_add(atan_fifth, atan_fifth)));
let pi_over_4 = decimal_compute_sub(four_atan_fifth, atan_239);
let pi = decimal_compute_add(pi_over_4, decimal_compute_add(pi_over_4, decimal_compute_add(pi_over_4, pi_over_4)));
PI_CACHE.with(|c: &std::cell::RefCell<Option<ComputeStorage>>| *c.borrow_mut() = Some(pi));
Ok(pi)
}
fn atan_small_direct(x: ComputeStorage) -> ComputeStorage {
if decimal_compute_is_zero(&x) {
return decimal_compute_zero();
}
let x_sq = decimal_compute_mul(x, x);
let mut term = x;
let mut sum = x;
let mut sign_positive = true;
let max_terms = (DECIMAL_COMPUTE_DP as u32 * 2) + 20;
for k in 1..=max_terms {
term = decimal_compute_mul(term, x_sq);
if decimal_compute_is_zero(&term) {
break;
}
sign_positive = !sign_positive;
let divisor = (2 * k as u64) + 1;
let contribution = decimal_compute_div_int(term, divisor);
if decimal_compute_is_zero(&contribution) {
break;
}
if sign_positive {
sum = decimal_compute_add(sum, contribution);
} else {
sum = decimal_compute_sub(sum, contribution);
}
}
sum
}
const fn max_trig_taylor_terms() -> u32 {
(DECIMAL_COMPUTE_DP as u32 / 2) + 20
}
pub fn decimal_sincos(x: ComputeStorage) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
if decimal_compute_is_zero(&x) {
return Ok((decimal_compute_zero(), decimal_compute_one()));
}
let pi = pi_at_decimal_compute()?;
let pi_half = decimal_compute_halve(pi);
let pi_quarter = decimal_compute_halve(pi_half);
let x_over_pi_half = decimal_compute_div(x, pi_half)?;
let k = round_to_int(x_over_pi_half);
let k_times_pi_half = mul_by_int(pi_half, k);
let r = decimal_compute_sub(x, k_times_pi_half);
let abs_r = decimal_compute_abs(r);
if decimal_compute_cmp(&abs_r, &pi_quarter) == std::cmp::Ordering::Greater {
}
let (sin_r, cos_r) = sincos_taylor(r);
let k_mod_4 = ((k % 4) + 4) % 4;
let (sin_x, cos_x) = match k_mod_4 {
0 => (sin_r, cos_r),
1 => (cos_r, decimal_compute_neg(sin_r)),
2 => (decimal_compute_neg(sin_r), decimal_compute_neg(cos_r)),
3 => (decimal_compute_neg(cos_r), sin_r),
_ => unreachable!(),
};
let _ = r; Ok((sin_x, cos_x))
}
pub fn decimal_sin(x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
decimal_sincos(x).map(|(s, _)| s)
}
pub fn decimal_cos(x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
decimal_sincos(x).map(|(_, c)| c)
}
fn sincos_taylor(r: ComputeStorage) -> (ComputeStorage, ComputeStorage) {
let r_sq = decimal_compute_mul(r, r);
let mut sin_term = r;
let mut sin_sum = r;
let mut sin_sign_positive = true;
let one = decimal_compute_one();
let mut cos_term = one;
let mut cos_sum = one;
let mut cos_sign_positive = true;
let max_terms = max_trig_taylor_terms();
for k in 1..=max_terms {
let k64 = k as u64;
sin_term = decimal_compute_mul(sin_term, r_sq);
let sin_div = (2 * k64) * (2 * k64 + 1);
sin_term = decimal_compute_div_int(sin_term, sin_div);
sin_sign_positive = !sin_sign_positive;
if !decimal_compute_is_zero(&sin_term) {
if sin_sign_positive {
sin_sum = decimal_compute_add(sin_sum, sin_term);
} else {
sin_sum = decimal_compute_sub(sin_sum, sin_term);
}
}
cos_term = decimal_compute_mul(cos_term, r_sq);
let cos_div = (2 * k64 - 1) * (2 * k64);
cos_term = decimal_compute_div_int(cos_term, cos_div);
cos_sign_positive = !cos_sign_positive;
if !decimal_compute_is_zero(&cos_term) {
if cos_sign_positive {
cos_sum = decimal_compute_add(cos_sum, cos_term);
} else {
cos_sum = decimal_compute_sub(cos_sum, cos_term);
}
}
if decimal_compute_is_zero(&sin_term) && decimal_compute_is_zero(&cos_term) {
break;
}
}
(sin_sum, cos_sum)
}
fn round_to_int(v: ComputeStorage) -> i64 {
let half = decimal_compute_halve(decimal_compute_one());
let rounded = if decimal_compute_is_negative(&v) {
decimal_compute_sub(v, half)
} else {
decimal_compute_add(v, half)
};
let scale = decimal_compute_one();
#[cfg(table_format = "q16_16")]
{ (rounded / scale) as i64 }
#[cfg(table_format = "q32_32")]
{ (rounded / scale) as i64 }
#[cfg(table_format = "q64_64")]
{ (rounded / scale).as_i128() as i64 }
#[cfg(table_format = "q128_128")]
{ (rounded / scale).as_i128() as i64 }
#[cfg(table_format = "q256_256")]
{ (rounded / scale).as_i128() as i64 }
}
fn mul_by_int(v: ComputeStorage, n: i64) -> ComputeStorage {
if n == 0 {
return decimal_compute_zero();
}
let negative = n < 0;
let n_abs = n.unsigned_abs();
let n_compute: ComputeStorage = {
#[cfg(table_format = "q16_16")]
{ n_abs as i64 }
#[cfg(table_format = "q32_32")]
{ n_abs as i128 }
#[cfg(table_format = "q64_64")]
{ crate::fixed_point::i256::I256::from_i128(n_abs as i128) }
#[cfg(table_format = "q128_128")]
{ crate::fixed_point::i512::I512::from_i128(n_abs as i128) }
#[cfg(table_format = "q256_256")]
{ crate::fixed_point::I1024::from_i128(n_abs as i128) }
};
let result = v * n_compute;
if negative {
decimal_compute_neg(result)
} else {
result
}
}
#[cfg(all(test, table_format = "q64_64"))]
mod tests {
use super::*;
use crate::fixed_point::i256::I256;
fn parse_decimal_str(s: &str) -> I256 {
let mut result = I256::from_i128(0);
let ten = I256::from_i128(10);
for ch in s.chars() {
let digit = ch.to_digit(10).expect("non-digit");
result = result * ten + I256::from_i128(digit as i128);
}
result
}
#[test]
fn sin_zero() {
let result = decimal_sin(decimal_compute_zero()).unwrap();
assert_eq!(result, decimal_compute_zero());
}
#[test]
fn cos_zero() {
let result = decimal_cos(decimal_compute_zero()).unwrap();
assert_eq!(result, decimal_compute_one());
}
#[test]
fn pi_value_matches_mpmath() {
let pi = pi_at_decimal_compute().unwrap();
let expected = parse_decimal_str("314159265358979323846264338327950288420");
let diff = if pi > expected { pi - expected } else { expected - pi };
let tolerance = I256::from_i128(1_000_000);
assert!(
diff < tolerance,
"π precision: got={:?} expected={:?} diff={:?}",
pi, expected, diff
);
}
#[test]
fn sin_one_mpmath() {
let one = decimal_compute_one();
let result = decimal_sin(one).unwrap();
let expected = parse_decimal_str("84147098480789650665250232163029899962");
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(10_000_000);
assert!(
diff < tolerance,
"sin(1) precision: got={:?} expected={:?} diff={:?}",
result, expected, diff
);
}
#[test]
fn cos_one_mpmath() {
let one = decimal_compute_one();
let result = decimal_cos(one).unwrap();
let expected = parse_decimal_str("54030230586813971740093660744297660373");
let diff = if result > expected { result - expected } else { expected - result };
let tolerance = I256::from_i128(10_000_000);
assert!(
diff < tolerance,
"cos(1) precision: got={:?} expected={:?} diff={:?}",
result, expected, diff
);
}
}