use crate::double_double::DoubleDouble;
use crate::sincospi::{GenSinCosPiBackend, SinCosPiBackend, reduce_pi_64};
use crate::tangent::tanpi::tanpi_eval;
use crate::tangent::tanpi_table::TANPI_K_PI_OVER_64;
#[cold]
fn cotpi_hard<B: SinCosPiBackend>(x: f64, tan_k: DoubleDouble, backend: &B) -> DoubleDouble {
const C: [(u64, u64); 6] = [
(0x3ca1a62632712fc8, 0x400921fb54442d18),
(0xbcc052338fbb4528, 0x4024abbce625be53),
(0x3ced42454c5f85b3, 0x404466bc6775aad9),
(0xbd00c7d6a971a560, 0x40645fff9b4b244d),
(0x3d205970eff53274, 0x40845f46e96c3a0b),
(0xbd3589489ad24fc4, 0x40a4630551cd123d),
];
let x2 = backend.exact_mult(x, x);
let mut tan_y = backend.quick_mul_add(
x2,
DoubleDouble::from_bit_pair(C[5]),
DoubleDouble::from_bit_pair(C[4]),
);
tan_y = backend.quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[3]));
tan_y = backend.quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[2]));
tan_y = backend.quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[1]));
tan_y = backend.quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[0]));
tan_y = backend.quick_mult_f64(tan_y, x);
let num = DoubleDouble::full_dd_add(tan_y, tan_k);
let den = backend.mul_add_f64(tan_y, -tan_k, 1.);
backend.div(den, num)
}
#[inline(always)]
fn cotpi_gen_impl<B: SinCosPiBackend>(x: f64, backend: B) -> f64 {
if x == 0. {
return if x.is_sign_negative() {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
let ax = x.to_bits() & 0x7fff_ffff_ffff_ffff;
if ax >= (0x7ffu64 << 52) {
if ax > (0x7ffu64 << 52) {
return x + x;
} return f64::NAN; }
let e: i32 = (ax >> 52) as i32 - 1023;
if e > 0 {
if e >= 52 {
return f64::copysign(f64::INFINITY, x);
}
let m = (ax & ((1u64 << 52) - 1)) | (1u64 << 52); let shift = 52 - e;
let frac = m & ((1u64 << shift) - 1);
if frac == (1u64 << (shift - 1)) {
return f64::copysign(0., x);
}
}
if ax <= 0x3cb0000000000000 {
const ONE_OVER_PI: DoubleDouble =
DoubleDouble::from_bit_pair((0xbc76b01ec5417056, 0x3fd45f306dc9c883));
if ax <= 0x3ca0000000000000 {
let e: i32 = (ax >> 52) as i32;
let sc = f64::from_bits((2045i64 - e as i64).wrapping_shl(52) as u64);
let dx = x * sc;
let q0 = backend.quick_mult(ONE_OVER_PI, DoubleDouble::from_quick_recip(dx));
let r = q0.to_f64() * sc;
return r;
}
let q0 = backend.quick_mult(ONE_OVER_PI, DoubleDouble::from_quick_recip(x));
let r = q0.to_f64();
return r;
}
let (y, k) = backend.arg_reduce_pi_64(x);
if y == 0.0 {
let km = (k.abs() & 63) as i32;
match km {
0 => return f64::copysign(f64::INFINITY, x), 32 => return f64::copysign(0., x), 16 => return f64::copysign(1.0, x), 48 => return -f64::copysign(1.0, x), _ => {}
}
}
let tan_k = DoubleDouble::from_bit_pair(TANPI_K_PI_OVER_64[((k as u64) & 127) as usize]);
let tan_y = tanpi_eval(y, &backend);
let num = DoubleDouble::add(tan_k, tan_y);
let den = backend.mul_add_f64(tan_y, -tan_k, 1.);
let tan_value = backend.div(den, num);
let err = backend.fma(
tan_value.hi,
f64::from_bits(0x3bf0000000000000), f64::from_bits(0x3b60000000000000), );
let ub = tan_value.hi + (tan_value.lo + err);
let lb = tan_value.hi + (tan_value.lo - err);
if ub == lb {
return tan_value.to_f64();
}
cotpi_hard(y, tan_k, &backend).to_f64()
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx", enable = "fma")]
unsafe fn cotpi_fma_impl(x: f64) -> f64 {
use crate::sincospi::FmaSinCosPiBackend;
cotpi_gen_impl(x, FmaSinCosPiBackend {})
}
pub fn f_cotpi(x: f64) -> f64 {
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
cotpi_gen_impl(x, GenSinCosPiBackend {})
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use std::sync::OnceLock;
static EXECUTOR: OnceLock<unsafe fn(f64) -> f64> = OnceLock::new();
let q = EXECUTOR.get_or_init(|| {
if std::arch::is_x86_feature_detected!("avx")
&& std::arch::is_x86_feature_detected!("fma")
{
cotpi_fma_impl
} else {
fn def_cotpi(x: f64) -> f64 {
cotpi_gen_impl(x, GenSinCosPiBackend {})
}
def_cotpi
}
});
unsafe { q(x) }
}
}
#[inline]
pub(crate) fn cotpi_core(x: f64) -> DoubleDouble {
let (y, k) = reduce_pi_64(x);
let tan_k = DoubleDouble::from_bit_pair(TANPI_K_PI_OVER_64[((k as u64) & 127) as usize]);
let tan_y = tanpi_eval(y, &GenSinCosPiBackend {});
let num = DoubleDouble::add(tan_k, tan_y);
let den = DoubleDouble::mul_add_f64(tan_y, -tan_k, 1.);
DoubleDouble::div(den, num)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cotpi() {
assert_eq!(f_cotpi(3.382112265043898e-306), 9.411570676518013e304);
assert_eq!(f_cotpi(0.0431431231), 7.332763436038805);
assert_eq!(f_cotpi(-0.0431431231), -7.332763436038805);
assert_eq!(f_cotpi(0.52324), -0.07314061937774036);
assert!(f_cotpi(f64::INFINITY).is_nan());
assert!(f_cotpi(f64::NAN).is_nan());
assert!(f_cotpi(f64::NEG_INFINITY).is_nan());
}
}