use bytemuck::cast;
use wide::{f32x8, i32x8, CmpEq, CmpGt, CmpLe, CmpLt};
use crate::single::{
COS_P, EXP_HI, EXP_LO, EXP_P, FRAC_2_PI, LG1, LG2, LG3, LG4, LN2_HI, LN2_LO, LOG2E, PIO2_1,
PIO2_2, PIO2_3, SIN_P, SQRT2_BITS,
};
#[inline(always)]
fn splat(v: f32) -> f32x8 {
f32x8::splat(v)
}
#[inline(always)]
fn isplat(v: i32) -> i32x8 {
i32x8::splat(v)
}
#[inline(always)]
fn neg(x: f32x8) -> f32x8 {
x ^ splat(-0.0)
}
#[must_use]
pub fn round(x: f32x8) -> f32x8 {
const TOINT: f32 = 8_388_608.0; let bits: i32x8 = cast(x);
let e: i32x8 = (bits >> 23i32) & isplat(0xff);
let integral: f32x8 = cast(e.cmp_gt(isplat(0x7f + 23 - 1)));
let small: f32x8 = cast(e.cmp_lt(isplat(0x7f - 1)));
let sign = x & splat(-0.0);
let ax = x.abs();
let y0 = ax + splat(TOINT) - splat(TOINT) - ax;
let y_gt = y0 + ax - splat(1.0);
let y_le = y0 + ax + splat(1.0);
let y_mid = y0 + ax;
let y = y0
.cmp_gt(splat(0.5))
.blend(y_gt, y0.cmp_le(splat(-0.5)).blend(y_le, y_mid));
let y = y | sign;
integral.blend(x, small.blend(sign, y))
}
#[must_use]
pub fn sqrt(x: f32x8) -> f32x8 {
let bad = x.is_nan() | x.cmp_lt(splat(0.0));
bad.blend(splat(f32::NAN), x.sqrt())
}
#[inline(always)]
fn sin_poly(r: f32x8) -> f32x8 {
let z = r * r;
let p = ((splat(SIN_P[0]) * z + splat(SIN_P[1])) * z + splat(SIN_P[2])) * z * r;
r + p
}
#[inline(always)]
fn cos_poly(r: f32x8) -> f32x8 {
let z = r * r;
let p = ((splat(COS_P[0]) * z + splat(COS_P[1])) * z + splat(COS_P[2])) * z * z;
splat(1.0) - splat(0.5) * z + p
}
#[inline(always)]
fn reduce_pio2(x: f32x8) -> (i32x8, f32x8) {
let kf = round(x * splat(FRAC_2_PI));
let r = ((x - kf * splat(PIO2_1)) - kf * splat(PIO2_2)) - kf * splat(PIO2_3);
(kf.trunc_int() & isplat(3), r)
}
#[inline(always)]
fn quadrant_masks(k: i32x8) -> (f32x8, f32x8, f32x8) {
(
cast(k.cmp_eq(isplat(0))),
cast(k.cmp_eq(isplat(1))),
cast(k.cmp_eq(isplat(2))),
)
}
#[must_use]
pub fn sin(x: f32x8) -> f32x8 {
let (k, r) = reduce_pio2(x);
let (m0, m1, m2) = quadrant_masks(k);
let s = sin_poly(r);
let c = cos_poly(r);
let v = m0.blend(s, m1.blend(c, m2.blend(neg(s), neg(c))));
canon(x, v)
}
#[inline(always)]
fn canon(x: f32x8, v: f32x8) -> f32x8 {
let nan = splat(f32::NAN);
(v.is_nan() | !x.is_finite()).blend(nan, v)
}
#[must_use]
pub fn cos(x: f32x8) -> f32x8 {
let (k, r) = reduce_pio2(x);
let (m0, m1, m2) = quadrant_masks(k);
let s = sin_poly(r);
let c = cos_poly(r);
let v = m0.blend(c, m1.blend(neg(s), m2.blend(neg(c), s)));
canon(x, v)
}
#[must_use]
pub fn sin_cos(x: f32x8) -> (f32x8, f32x8) {
let (k, r) = reduce_pio2(x);
let (m0, m1, m2) = quadrant_masks(k);
let s = sin_poly(r);
let c = cos_poly(r);
let sv = m0.blend(s, m1.blend(c, m2.blend(neg(s), neg(c))));
let cv = m0.blend(c, m1.blend(neg(s), m2.blend(neg(c), s)));
(canon(x, sv), canon(x, cv))
}
#[inline(always)]
fn pow2i(k: i32x8) -> f32x8 {
cast((k + isplat(127)) << 23i32)
}
#[must_use]
pub fn exp(x: f32x8) -> f32x8 {
let nan = x.is_nan();
let over = x.cmp_gt(splat(EXP_HI));
let under = x.cmp_lt(splat(EXP_LO));
let xc = over.blend(splat(EXP_HI), under.blend(splat(EXP_LO), x));
let kf = round(xc * splat(LOG2E));
let k = kf.trunc_int();
let r = (xc - kf * splat(LN2_HI)) - kf * splat(LN2_LO);
let p = (((((splat(EXP_P[0]) * r + splat(EXP_P[1])) * r + splat(EXP_P[2])) * r
+ splat(EXP_P[3]))
* r
+ splat(EXP_P[4]))
* r
+ splat(0.5))
* r
* r
+ r
+ splat(1.0);
let k_hi = k.max(isplat(-126)).min(isplat(127));
let k_lo = k - k_hi;
let v = p * pow2i(k_hi) * pow2i(k_lo);
nan.blend(
splat(f32::NAN),
over.blend(splat(f32::INFINITY), under.blend(splat(0.0), v)),
)
}
#[must_use]
pub fn ln(x: f32x8) -> f32x8 {
let bad = x.is_nan() | x.cmp_lt(splat(0.0));
let zero = x.cmp_eq(splat(0.0));
let inf = x.cmp_eq(splat(f32::INFINITY));
let bits: i32x8 = cast(x);
let sub = bits.cmp_lt(isplat(0x0080_0000));
let scaled: i32x8 = cast(x * splat(33_554_432.0)); let bits = sub.blend(scaled, bits);
let k = sub.blend(isplat(-25), isplat(0));
let k = k + ((bits >> 23i32) - isplat(127));
let bits = (bits & isplat(0x007f_ffff)) | isplat(0x3f80_0000); let ge = !bits.cmp_lt(isplat(SQRT2_BITS as i32));
let bits = ge.blend(bits - isplat(0x0080_0000), bits);
let k = ge.blend(k + isplat(1), k);
let m: f32x8 = cast(bits);
let f = m - splat(1.0);
let s = f / (splat(2.0) + f);
let z = s * s;
let w = z * z;
let t1 = w * (splat(LG2) + w * splat(LG4));
let t2 = z * (splat(LG1) + w * splat(LG3));
let r = t2 + t1;
let hfsq = splat(0.5) * f * f;
let kf = f32x8::from_i32x8(k);
let v = kf * splat(LN2_HI) - ((hfsq - (s * (hfsq + r) + kf * splat(LN2_LO))) - f);
bad.blend(
splat(f32::NAN),
zero.blend(splat(f32::NEG_INFINITY), inf.blend(splat(f32::INFINITY), v)),
)
}
#[inline(always)]
fn map(x: f32x8, f: impl Fn(f32) -> f32) -> f32x8 {
let a = x.to_array();
f32x8::new([
f(a[0]),
f(a[1]),
f(a[2]),
f(a[3]),
f(a[4]),
f(a[5]),
f(a[6]),
f(a[7]),
])
}
#[inline(always)]
fn map2(x: f32x8, y: f32x8, f: impl Fn(f32, f32) -> f32) -> f32x8 {
let a = x.to_array();
let b = y.to_array();
f32x8::new([
f(a[0], b[0]),
f(a[1], b[1]),
f(a[2], b[2]),
f(a[3], b[3]),
f(a[4], b[4]),
f(a[5], b[5]),
f(a[6], b[6]),
f(a[7], b[7]),
])
}
#[must_use]
pub fn atan2(y: f32x8, x: f32x8) -> f32x8 {
map2(y, x, crate::atan2)
}
#[must_use]
pub fn atan(x: f32x8) -> f32x8 {
map(x, crate::atan)
}
#[must_use]
pub fn asin(x: f32x8) -> f32x8 {
map(x, crate::asin)
}
#[must_use]
pub fn acos(x: f32x8) -> f32x8 {
map(x, crate::acos)
}
#[must_use]
pub fn tan(x: f32x8) -> f32x8 {
map(x, crate::tan)
}
#[must_use]
pub fn tanh(x: f32x8) -> f32x8 {
map(x, crate::tanh)
}
#[must_use]
pub fn powf(x: f32x8, y: f32x8) -> f32x8 {
map2(x, y, crate::powf)
}
#[must_use]
pub fn cbrt(x: f32x8) -> f32x8 {
map(x, crate::cbrt)
}
#[must_use]
pub fn hypot(x: f32x8, y: f32x8) -> f32x8 {
map2(x, y, crate::hypot)
}