use super::reduce_pi_2::round_fi;
use crate::double::{NormDouble, SemiDouble};
use crate::traits::{CastFrom, CastInto, Float, Int as _, Like, SInt};
pub(crate) trait Reduce90Deg<L = Like<Self>>: Float {
fn deg_to_rad() -> Self;
fn deg_to_rad_ex() -> SemiDouble<Self>;
type SRaw: SInt + CastInto<Self> + CastFrom<Self::Raw>;
}
pub(crate) fn reduce_90_deg<F: Reduce90Deg>(x: F) -> (u8, NormDouble<F>) {
let xabs = x.abs();
let xexp = x.exponent();
if xabs <= F::cast_from(45u32) {
let scale = F::exp2i_fast(F::Exp::cast_from(F::MANT_BITS));
let descale = F::exp2i_fast(-F::Exp::cast_from(F::MANT_BITS));
let sx = SemiDouble::new(x * scale);
let y = (sx * F::deg_to_rad_ex()).pmul1(descale).to_norm();
(0, y)
} else if xexp <= F::Exp::cast_from(F::MANT_BITS - 4).min(F::Exp::from(31i8)) {
let (f_n, n) = round_fi(x * (F::one() / F::cast_from(90u32)));
let ydeg = x - f_n * F::cast_from(90u32);
let ydeg = SemiDouble::new(ydeg);
let y = (ydeg * F::deg_to_rad_ex()).to_norm();
(n as u8 & 3, y)
} else if xexp < F::Exp::cast_from(F::BITS - 1) {
let xraw = x.to_raw();
let (ixint, xfrac) = if xexp <= F::Exp::cast_from(F::MANT_BITS) {
let shift: u8 = (F::Exp::cast_from(F::MANT_BITS) - xexp).cast_into();
let fxint = F::from_raw(xraw & !F::SIGN_MASK & (F::Raw::MAX << shift));
let ixint = x.mant() >> shift;
let xfrac = xabs - fxint;
(ixint, xfrac)
} else {
let shift: u8 = (xexp - F::Exp::cast_from(F::MANT_BITS)).cast_into();
let ixint = x.mant() << shift;
(ixint, F::ZERO)
};
let n = (ixint + F::Raw::from(45u8)) / F::Raw::from(90u8);
let irem = F::SRaw::cast_from(ixint) - F::SRaw::cast_from(F::Raw::from(90u8) * n);
let frem: F = irem.cast_into();
let ydeg = SemiDouble::new(xfrac + frem);
let y = (ydeg * F::deg_to_rad_ex()).to_norm();
let n: u8 = n.cast_into();
if x.sign() {
(n.wrapping_neg() & 3, -y)
} else {
(n & 3, y)
}
} else {
let xm = x.mant();
let xe = xexp - F::Exp::cast_from(F::MANT_BITS);
let xe: u16 = xe.cast_into();
const EXP2_MOD45: [u8; 12] = [1, 2, 4, 8, 16, 32, 19, 38, 31, 17, 34, 23];
debug_assert!(xe > 3);
let t = xm * F::Raw::from(EXP2_MOD45[usize::from((xe - 3) % 12)]);
let rem45: u16 = (t % F::Raw::from(45u8)).cast_into();
let rem360 = rem45 * 8;
let mut n = (rem360 / 90) as u8;
let mut rem90 = (rem360 % 90) as i16;
if rem90 >= 45 {
n += 1;
rem90 -= 90;
}
let ydeg = SemiDouble::with_parts(F::cast_from(rem90), F::ZERO);
let y = (ydeg * F::deg_to_rad_ex()).to_norm();
if x.sign() {
(n.wrapping_neg() & 3, -y)
} else {
(n & 3, y)
}
}
}