#![deny(missing_docs)]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
use core::{
    borrow::Borrow,
    cmp::Ordering,
    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};
use num_traits::{ConstOne, ConstZero, Inv, Num, One, Zero};
#[cfg(any(feature = "std", feature = "libm"))]
use {
    core::num::FpCategory,
    num_traits::{float::Float, FloatConst},
};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Quaternion<T> {
    pub w: T,
    pub x: T,
    pub y: T,
    pub z: T,
}
pub type Q32 = Quaternion<f32>;
pub type Q64 = Quaternion<f64>;
impl<T> Quaternion<T> {
    #[inline]
    pub const fn new(w: T, x: T, y: T, z: T) -> Self {
        Self { w, x, y, z }
    }
}
impl<T> Quaternion<T>
where
    T: ConstZero,
{
    pub const ZERO: Self = Self::new(T::ZERO, T::ZERO, T::ZERO, T::ZERO);
}
impl<T> ConstZero for Quaternion<T>
where
    T: ConstZero,
{
    const ZERO: Self = Self::ZERO;
}
impl<T> Zero for Quaternion<T>
where
    T: Zero,
{
    #[inline]
    fn zero() -> Self {
        Self::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
    }
    #[inline]
    fn is_zero(&self) -> bool {
        self.w.is_zero() && self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
    }
    #[inline]
    fn set_zero(&mut self) {
        self.w.set_zero();
        self.x.set_zero();
        self.y.set_zero();
        self.z.set_zero();
    }
}
impl<T> Quaternion<T>
where
    T: ConstZero + ConstOne,
{
    pub const ONE: Self = Self::new(T::ONE, T::ZERO, T::ZERO, T::ZERO);
    pub const I: Self = Self::new(T::ZERO, T::ONE, T::ZERO, T::ZERO);
    pub const J: Self = Self::new(T::ZERO, T::ZERO, T::ONE, T::ZERO);
    pub const K: Self = Self::new(T::ZERO, T::ZERO, T::ZERO, T::ONE);
}
impl<T> ConstOne for Quaternion<T>
where
    T: ConstZero + ConstOne + Num + Clone,
{
    const ONE: Self = Self::ONE;
}
impl<T> One for Quaternion<T>
where
    T: Num + Clone,
{
    #[inline]
    fn one() -> Self {
        Self::new(One::one(), Zero::zero(), Zero::zero(), Zero::zero())
    }
    #[inline]
    fn is_one(&self) -> bool {
        self.w.is_one() && self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
    }
    #[inline]
    fn set_one(&mut self) {
        self.w.set_one();
        self.x.set_zero();
        self.y.set_zero();
        self.z.set_zero();
    }
}
impl<T> Quaternion<T>
where
    T: Zero + One,
{
    #[inline]
    pub fn i() -> Self {
        Self::new(T::zero(), T::one(), T::zero(), T::zero())
    }
    #[inline]
    pub fn j() -> Self {
        Self::new(T::zero(), T::zero(), T::one(), T::zero())
    }
    #[inline]
    pub fn k() -> Self {
        Self::new(T::zero(), T::zero(), T::zero(), T::one())
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
    T: Float,
{
    #[inline]
    pub fn nan() -> Self {
        let nan = T::nan();
        Self::new(nan, nan, nan, nan)
    }
}
impl<T> Quaternion<T>
where
    T: Clone + Mul<T, Output = T> + Add<T, Output = T>,
{
    #[inline]
    pub fn norm_sqr(&self) -> T {
        (self.w.clone() * self.w.clone() + self.y.clone() * self.y.clone())
            + (self.x.clone() * self.x.clone() + self.z.clone() * self.z.clone())
    }
}
impl<T> Quaternion<T>
where
    T: Clone + Neg<Output = T>,
{
    #[inline]
    pub fn conj(&self) -> Self {
        Self::new(
            self.w.clone(),
            -self.x.clone(),
            -self.y.clone(),
            -self.z.clone(),
        )
    }
}
impl<T> Quaternion<T>
where
    for<'a> &'a Self: Inv<Output = Quaternion<T>>,
{
    #[inline]
    pub fn inv(&self) -> Self {
        Inv::inv(self)
    }
}
impl<T> Inv for &Quaternion<T>
where
    T: Clone + Neg<Output = T> + Num,
{
    type Output = Quaternion<T>;
    #[inline]
    fn inv(self) -> Self::Output {
        let norm_sqr = self.norm_sqr();
        Quaternion::new(
            self.w.clone() / norm_sqr.clone(),
            -self.x.clone() / norm_sqr.clone(),
            -self.y.clone() / norm_sqr.clone(),
            -self.z.clone() / norm_sqr,
        )
    }
}
impl<T> Inv for Quaternion<T>
where
    for<'a> &'a Self: Inv<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn inv(self) -> Self::Output {
        Inv::inv(&self)
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
    T: Float,
{
    #[inline]
    pub fn norm(self) -> T {
        let Self { w, x, y, z } = self;
        let norm_sqr = w * w + x * x + y * y + z * z;
        if norm_sqr.is_normal() {
            norm_sqr.sqrt()
        } else {
            self.handle_non_normal_cases(norm_sqr)
        }
    }
    fn handle_non_normal_cases(self, norm_sqr: T) -> T {
        debug_assert!(!norm_sqr.is_normal());
        let s = T::min_positive_value();
        if norm_sqr < s {
            if self.is_zero() {
                T::zero()
            } else {
                (self / s).fast_norm() * s
            }
        } else if norm_sqr.is_infinite() {
            (self * s).fast_norm() / s
        } else {
            debug_assert!(norm_sqr.is_nan(), "norm_sqr is not NaN");
            T::nan()
        }
    }
    #[inline]
    pub fn fast_norm(self) -> T {
        self.norm_sqr().sqrt()
    }
    #[inline]
    pub fn normalize(self) -> Option<UnitQuaternion<T>> {
        let norm = self.norm();
        match norm.classify() {
            FpCategory::Normal | FpCategory::Subnormal => Some(UnitQuaternion(self / norm)),
            _ => None,
        }
    }
}
impl<T> From<T> for Quaternion<T>
where
    T: Zero,
{
    #[inline]
    fn from(a: T) -> Self {
        Self::new(a, T::zero(), T::zero(), T::zero())
    }
}
impl<'a, T> From<&'a T> for Quaternion<T>
where
    T: Clone + Zero,
{
    #[inline]
    fn from(a: &T) -> Self {
        From::from(a.clone())
    }
}
impl<T> From<UnitQuaternion<T>> for Quaternion<T> {
    #[inline]
    fn from(q: UnitQuaternion<T>) -> Self {
        q.0
    }
}
impl<'a, T> From<&'a UnitQuaternion<T>> for &'a Quaternion<T> {
    #[inline]
    fn from(q: &'a UnitQuaternion<T>) -> Self {
        &q.0
    }
}
impl<T> Add<Quaternion<T>> for Quaternion<T>
where
    T: Add<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: Quaternion<T>) -> Self::Output {
        Self::new(
            self.w + rhs.w,
            self.x + rhs.x,
            self.y + rhs.y,
            self.z + rhs.z,
        )
    }
}
impl<T> Add<T> for Quaternion<T>
where
    T: Add<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: T) -> Self::Output {
        Self::new(self.w + rhs, self.x, self.y, self.z)
    }
}
impl<T> Add<UnitQuaternion<T>> for Quaternion<T>
where
    T: Add<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self + rhs.0
    }
}
impl<T> Sub<Quaternion<T>> for Quaternion<T>
where
    T: Sub<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: Quaternion<T>) -> Self::Output {
        Self::new(
            self.w - rhs.w,
            self.x - rhs.x,
            self.y - rhs.y,
            self.z - rhs.z,
        )
    }
}
impl<T> Sub<T> for Quaternion<T>
where
    T: Sub<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: T) -> Self::Output {
        Self::new(self.w - rhs, self.x, self.y, self.z)
    }
}
impl<T> Sub<UnitQuaternion<T>> for Quaternion<T>
where
    T: Sub<T, Output = T>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self - rhs.0
    }
}
impl<T> Mul<Quaternion<T>> for Quaternion<T>
where
    T: Add<T, Output = T> + Sub<T, Output = T> + Mul<T, Output = T> + Clone,
{
    type Output = Quaternion<T>;
    #[inline]
    fn mul(self, rhs: Quaternion<T>) -> Self::Output {
        let a = self.w.clone() * rhs.w.clone()
            - self.x.clone() * rhs.x.clone()
            - self.y.clone() * rhs.y.clone()
            - self.z.clone() * rhs.z.clone();
        let b = self.w.clone() * rhs.x.clone()
            + self.x.clone() * rhs.w.clone()
            + self.y.clone() * rhs.z.clone()
            - self.z.clone() * rhs.y.clone();
        let c = self.w.clone() * rhs.y.clone() - self.x.clone() * rhs.z.clone()
            + self.y.clone() * rhs.w.clone()
            + self.z.clone() * rhs.x.clone();
        let d = self.w * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.w;
        Self::new(a, b, c, d)
    }
}
impl<T> Mul<T> for Quaternion<T>
where
    T: Mul<T, Output = T> + Clone,
{
    type Output = Quaternion<T>;
    #[inline]
    fn mul(self, rhs: T) -> Self::Output {
        Self::new(
            self.w * rhs.clone(),
            self.x * rhs.clone(),
            self.y * rhs.clone(),
            self.z * rhs,
        )
    }
}
impl<T> Mul<UnitQuaternion<T>> for Quaternion<T>
where
    Quaternion<T>: Mul<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn mul(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self * rhs.0
    }
}
impl<T> Div<Quaternion<T>> for Quaternion<T>
where
    T: Num + Clone + Neg<Output = T>,
{
    type Output = Quaternion<T>;
    #[allow(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn div(self, rhs: Quaternion<T>) -> Self::Output {
        self * rhs.inv()
    }
}
impl<T> Div<T> for Quaternion<T>
where
    T: Div<T, Output = T> + Clone,
{
    type Output = Quaternion<T>;
    #[inline]
    fn div(self, rhs: T) -> Self::Output {
        Self::new(
            self.w / rhs.clone(),
            self.x / rhs.clone(),
            self.y / rhs.clone(),
            self.z / rhs,
        )
    }
}
impl<T> Div<UnitQuaternion<T>> for Quaternion<T>
where
    Quaternion<T>: Mul<Output = Quaternion<T>>,
    T: Neg<Output = T> + Clone,
{
    type Output = Quaternion<T>;
    #[allow(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn div(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self * rhs.0.conj()
    }
}
macro_rules! impl_bin_op_assign {
    (impl $bin_op_assign_trait:ident, $bin_op_assign:ident, $bin_op_trait:ident, $bin_op:ident) => {
        impl<T, S> $bin_op_assign_trait<S> for Quaternion<T>
        where
            Self: $bin_op_trait<S, Output = Self> + Clone,
        {
            #[inline]
            fn $bin_op_assign(&mut self, other: S) {
                *self = self.clone().$bin_op(other);
            }
        }
    };
}
impl_bin_op_assign!(impl AddAssign, add_assign, Add, add);
impl_bin_op_assign!(impl SubAssign, sub_assign, Sub, sub);
impl_bin_op_assign!(impl MulAssign, mul_assign, Mul, mul);
impl_bin_op_assign!(impl DivAssign, div_assign, Div, div);
macro_rules! impl_op_with_ref {
    (impl<$T:ident> $bin_op_trait:ident::$bin_op:ident for $lhs_type:ty, $rhs_type:ty) => {
        impl<$T> $bin_op_trait<&$rhs_type> for $lhs_type
        where
            Self: $bin_op_trait<$rhs_type>,
            $rhs_type: Clone,
        {
            type Output = <Self as $bin_op_trait<$rhs_type>>::Output;
            fn $bin_op(self, rhs: &$rhs_type) -> Self::Output {
                self.$bin_op(rhs.clone())
            }
        }
        impl<$T> $bin_op_trait<&$rhs_type> for &$lhs_type
        where
            $lhs_type: $bin_op_trait<$rhs_type> + Clone,
            $rhs_type: Clone,
        {
            type Output = <$lhs_type as $bin_op_trait<$rhs_type>>::Output;
            fn $bin_op(self, rhs: &$rhs_type) -> Self::Output {
                self.clone().$bin_op(rhs.clone())
            }
        }
        impl<$T> $bin_op_trait<$rhs_type> for &$lhs_type
        where
            $lhs_type: $bin_op_trait<$rhs_type> + Clone,
        {
            type Output = <$lhs_type as $bin_op_trait<$rhs_type>>::Output;
            fn $bin_op(self, rhs: $rhs_type) -> Self::Output {
                self.clone().$bin_op(rhs)
            }
        }
    };
}
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, T);
macro_rules! impl_ops_lhs_real {
    ($($real:ty),*) => {
        $(
        impl Add<Quaternion<$real>> for $real {
            type Output = Quaternion<$real>;
            #[inline]
            fn add(self, mut rhs: Quaternion<$real>) -> Self::Output {
                rhs.w += self;
                rhs
            }
        }
        impl Sub<Quaternion<$real>> for $real {
            type Output = Quaternion<$real>;
            #[inline]
            fn sub(self, rhs: Quaternion<$real>) -> Self::Output {
                let zero = <$real>::zero();
                Self::Output::new(self - rhs.w, zero - rhs.x, zero - rhs.y, zero - rhs.z)
            }
        }
        impl Mul<Quaternion<$real>> for $real {
            type Output = Quaternion<$real>;
            #[inline]
            fn mul(self, rhs: Quaternion<$real>) -> Self::Output {
                Self::Output::new(self * rhs.w, self * rhs.x, self * rhs.y, self * rhs.z)
            }
        }
    )*
    };
}
impl_ops_lhs_real!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64);
impl Div<Q32> for f32 {
    type Output = Q32;
    #[inline]
    fn div(mut self, rhs: Q32) -> Self::Output {
        self /= rhs.norm_sqr();
        Self::Output::new(self * rhs.w, self * -rhs.x, self * -rhs.y, self * -rhs.z)
    }
}
impl Div<Q64> for f64 {
    type Output = Q64;
    #[inline]
    fn div(mut self, rhs: Q64) -> Self::Output {
        self /= rhs.norm_sqr();
        Self::Output::new(self * rhs.w, self * -rhs.x, self * -rhs.y, self * -rhs.z)
    }
}
impl<T> Neg for Quaternion<T>
where
    T: Neg<Output = T>,
{
    type Output = Self;
    #[inline]
    fn neg(self) -> Self::Output {
        Self::new(-self.w, -self.x, -self.y, -self.z)
    }
}
impl<T> Quaternion<T>
where
    T: Add<T, Output = T> + Mul<T, Output = T>,
{
    #[inline]
    pub fn dot(self, other: Self) -> T {
        self.w * other.w + self.y * other.y + (self.x * other.x + self.z * other.z)
    }
}
impl<T> Quaternion<T>
where
    T: Num + Clone,
{
    pub fn powu(&self, mut n: u32) -> Self {
        if n == 0 {
            Self::one()
        } else {
            let mut base = self.clone();
            while n & 1 == 0 {
                n /= 2;
                base = base.clone() * base;
            }
            if n == 1 {
                return base;
            }
            let mut acc = base.clone();
            while n > 1 {
                n /= 2;
                base = base.clone() * base;
                if n & 1 == 1 {
                    acc *= base.clone();
                }
            }
            acc
        }
    }
}
impl<T> Quaternion<T>
where
    T: Clone + Num + Neg<Output = T>,
{
    #[inline]
    pub fn powi(&self, n: i32) -> Self {
        if n >= 0 {
            self.powu(n as u32)
        } else {
            self.inv().powu(n.wrapping_neg() as u32)
        }
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
    T: Float + FloatConst,
{
    pub fn exp(self) -> Self {
        let one = T::one();
        let two = one + one;
        let four = two + two;
        let half = one / two;
        let quarter = one / four;
        let inf = T::infinity();
        let result_norm = self.w.exp();
        match result_norm.partial_cmp(&inf) {
            Some(Ordering::Less) => {
                if result_norm.is_zero() {
                    return Self::zero();
                }
                let sqr_angle = self.x * self.x + self.y * self.y + self.z * self.z;
                if sqr_angle <= T::epsilon() {
                    let w = result_norm;
                    let x = result_norm * self.x;
                    let y = result_norm * self.y;
                    let z = result_norm * self.z;
                    Self::new(w, x, y, z)
                } else {
                    let angle = sqr_angle.sqrt();
                    let cos_angle = angle.cos();
                    let sinc_angle = angle.sin() / angle;
                    let w = result_norm * cos_angle;
                    let x = result_norm * self.x * sinc_angle;
                    let y = result_norm * self.y * sinc_angle;
                    let z = result_norm * self.z * sinc_angle;
                    Self::new(w, x, y, z)
                }
            }
            Some(_) => {
                let map = |a: T| {
                    if a.is_zero() {
                        a
                    } else {
                        inf.copysign(a)
                    }
                };
                let sqr_angle = self.x * self.x + self.y * self.y + self.z * self.z;
                if sqr_angle < T::PI() * T::PI() * quarter {
                    Self::new(inf, map(self.x), map(self.y), map(self.z))
                } else if sqr_angle.is_finite() {
                    let angle = sqr_angle.sqrt();
                    let angle_revolutions_fract = (angle * T::FRAC_1_PI() * half).fract();
                    let cos_angle_signum = (angle_revolutions_fract - half).abs() - quarter;
                    let sin_angle_signum = one.copysign(half - angle_revolutions_fract);
                    Self::new(
                        inf.copysign(cos_angle_signum),
                        map(self.x) * sin_angle_signum,
                        map(self.y) * sin_angle_signum,
                        map(self.z) * sin_angle_signum,
                    )
                } else {
                    assert!(sqr_angle.is_infinite() || sqr_angle.is_nan());
                    Self::nan()
                }
            }
            None => {
                debug_assert!(result_norm.is_nan());
                Self::nan()
            }
        }
    }
    fn is_finite(&self) -> bool {
        self.w.is_finite() && self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
    }
    pub fn ln(self) -> Self {
        let sqr_norm_im = self.x * self.x + self.y * self.y + self.z * self.z;
        let norm_sqr = self.w * self.w + sqr_norm_im;
        match norm_sqr.classify() {
            FpCategory::Normal => {
                let w = norm_sqr.ln() * T::from(0.5).expect("Conversion failed");
                if sqr_norm_im <= self.w * self.w * T::epsilon() {
                    if self.w.is_sign_positive() {
                        let x = self.x / self.w;
                        let y = self.y / self.w;
                        let z = self.z / self.w;
                        Self::new(w, x, y, z)
                    } else if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
                        Self::new(w, T::PI().copysign(self.x), self.y, self.z)
                    } else {
                        let norm_im = if sqr_norm_im.is_normal() {
                            sqr_norm_im.sqrt()
                        } else {
                            let f = T::min_positive_value().sqrt();
                            let xf = self.x / f;
                            let yf = self.y / f;
                            let zf = self.z / f;
                            let sqr_sum = xf * xf + yf * yf + zf * zf;
                            sqr_sum.sqrt() * f
                        };
                        let f = T::PI() / norm_im + self.w.recip();
                        Self::new(w, f * self.x, f * self.y, f * self.z)
                    }
                } else {
                    let norm_im = if sqr_norm_im.is_normal() {
                        sqr_norm_im.sqrt()
                    } else {
                        let f = T::min_positive_value().sqrt();
                        let xf = self.x / f;
                        let yf = self.y / f;
                        let zf = self.z / f;
                        let sqr_sum = xf * xf + yf * yf + zf * zf;
                        sqr_sum.sqrt() * f
                    };
                    let angle = norm_im.atan2(self.w);
                    let x = self.x * angle / norm_im;
                    let y = self.y * angle / norm_im;
                    let z = self.z * angle / norm_im;
                    Self::new(w, x, y, z)
                }
            }
            FpCategory::Zero if self.is_zero() => {
                let x = if self.w.is_sign_positive() {
                    self.x
                } else {
                    T::PI().copysign(self.x)
                };
                Self::new(T::neg_infinity(), x, self.y, self.z)
            }
            FpCategory::Nan => Self::nan(),
            FpCategory::Infinite => {
                if self.is_finite() {
                    let factor = T::one() / T::max_value().sqrt();
                    (self * factor).ln() - factor.ln()
                } else {
                    let f = |r: T| {
                        if r.is_infinite() {
                            r.signum()
                        } else {
                            T::zero().copysign(r)
                        }
                    };
                    let q = Self::new(f(self.w), f(self.x), f(self.y), f(self.z));
                    q.ln() + T::infinity()
                }
            }
            _ => {
                let factor = T::one() / T::min_positive_value();
                (self * factor).ln() - factor.ln()
            }
        }
    }
    pub fn sqrt(self) -> Self {
        let zero = T::zero();
        let one = T::one();
        let two = one + one;
        let half = one / two;
        let inf = T::infinity();
        let s = one / T::min_positive_value(); let norm_sqr = self.norm_sqr();
        match norm_sqr.classify() {
            FpCategory::Normal => {
                let norm = norm_sqr.sqrt();
                if self.w.is_sign_positive() {
                    let wx2 = ((self.w + norm) * two).sqrt();
                    Self::new(wx2 * half, self.x / wx2, self.y / wx2, self.z / wx2)
                } else {
                    let im_norm_sqr = self.y * self.y + (self.x * self.x + self.z * self.z);
                    if im_norm_sqr >= T::min_positive_value() {
                        let wx2 = (im_norm_sqr * two / (norm - self.w)).sqrt();
                        Self::new(wx2 * half, self.x / wx2, self.y / wx2, self.z / wx2)
                    } else if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
                        Self::new(zero, (-self.w).sqrt().copysign(self.x), self.y, self.z)
                    } else {
                        let sx = s * self.x;
                        let sy = s * self.y;
                        let sz = s * self.z;
                        let im_norm = (sy * sy + (sx * sx + sz * sz)).sqrt() / s;
                        let w = im_norm / (half * (norm - self.w)).sqrt();
                        Self::new(w * half, self.x / w, self.y / w, self.z / w)
                    }
                }
            }
            FpCategory::Zero if self.is_zero() => Self::new(zero, self.x, self.y, self.z),
            FpCategory::Infinite => {
                if self.w == inf
                    || self.x.is_infinite()
                    || self.y.is_infinite()
                    || self.z.is_infinite()
                {
                    let f = |a: T| if a.is_infinite() { a } else { zero.copysign(a) };
                    Self::new(inf, f(self.x), f(self.y), f(self.z))
                } else if self.w == -inf {
                    Self::new(
                        zero,
                        inf.copysign(self.x),
                        zero.copysign(self.y),
                        zero.copysign(self.z),
                    )
                } else {
                    (self / s).sqrt() * s.sqrt()
                }
            }
            FpCategory::Nan => Self::nan(),
            _ => {
                (self * s).sqrt() / s.sqrt()
            }
        }
    }
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for Quaternion<T>
where
    T: serde::Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        (&self.w, &self.x, &self.y, &self.z).serialize(serializer)
    }
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for Quaternion<T>
where
    T: serde::Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let (w, x, y, z) = serde::Deserialize::deserialize(deserializer)?;
        Ok(Self::new(w, x, y, z))
    }
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UnitQuaternion<T>(Quaternion<T>);
pub type UQ32 = UnitQuaternion<f32>;
pub type UQ64 = UnitQuaternion<f64>;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EulerAngles<T> {
    pub roll: T,
    pub pitch: T,
    pub yaw: T,
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for EulerAngles<T>
where
    T: serde::Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        (&self.roll, &self.pitch, &self.yaw).serialize(serializer)
    }
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for EulerAngles<T>
where
    T: serde::Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let (roll, pitch, yaw) = serde::Deserialize::deserialize(deserializer)?;
        Ok(Self { roll, pitch, yaw })
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    pub fn from_euler_angles(roll: T, pitch: T, yaw: T) -> Self {
        let half = T::one() / (T::one() + T::one());
        let (sr, cr) = (roll * half).sin_cos();
        let (sp, cp) = (pitch * half).sin_cos();
        let (sy, cy) = (yaw * half).sin_cos();
        Self(Quaternion::new(
            cr * cp * cy + sr * sp * sy,
            sr * cp * cy - cr * sp * sy,
            cr * sp * cy + sr * cp * sy,
            cr * cp * sy - sr * sp * cy,
        ))
    }
    #[cfg(feature = "unstable")]
    pub fn from_euler_angles_struct(angles: EulerAngles<T>) -> Self {
        let EulerAngles { roll, pitch, yaw } = angles;
        Self::from_euler_angles(roll, pitch, yaw)
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float + FloatConst,
{
    pub fn to_euler_angles(&self) -> EulerAngles<T> {
        let &Self(Quaternion { w, x, y, z }) = self;
        let one = T::one();
        let two = one + one;
        let epsilon = T::epsilon();
        let half_pi = T::FRAC_PI_2();
        let sin_pitch = two * (w * y - z * x);
        if sin_pitch.abs() >= one - epsilon {
            let pitch = if sin_pitch >= one - epsilon {
                half_pi } else {
                -half_pi };
            let roll = T::zero();
            let yaw = T::atan2(two * (x * y + w * z), one - two * (y * y + z * z));
            EulerAngles { roll, pitch, yaw }
        } else {
            let pitch = sin_pitch.asin();
            let roll = T::atan2(two * (w * x + y * z), one - two * (x * x + y * y));
            let yaw = T::atan2(two * (w * z + x * y), one - two * (y * y + z * z));
            EulerAngles { roll, pitch, yaw }
        }
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    pub fn from_rotation_vector(v: &[T; 3]) -> Self {
        let sqr_norm = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
        let two = T::one() + T::one();
        match sqr_norm.classify() {
            FpCategory::Normal => {
                let norm = sqr_norm.sqrt();
                let (sine, cosine) = (norm / two).sin_cos();
                let f = sine / norm;
                Self(Quaternion::new(cosine, v[0] * f, v[1] * f, v[2] * f))
            }
            FpCategory::Zero | FpCategory::Subnormal => Self(Quaternion::new(
                T::one(),
                v[0] / two,
                v[1] / two,
                v[2] / two,
            )),
            FpCategory::Nan | FpCategory::Infinite => Self(Quaternion::nan()),
        }
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float + FloatConst,
{
    pub fn to_rotation_vector(&self) -> [T; 3] {
        let q = self.as_quaternion();
        let one = T::one();
        let two = one + one;
        let epsilon = T::epsilon();
        let small_abs_real_part = q.w.abs() < T::from(0.9).unwrap();
        let sin_half_angle = if small_abs_real_part {
            (one - q.w * q.w).sqrt()
        } else {
            (q.x * q.x + q.y * q.y + q.z * q.z).sqrt()
        };
        let angle = two
            * if small_abs_real_part {
                q.w.acos()
            } else if q.w.is_sign_positive() {
                if sin_half_angle < epsilon.sqrt() {
                    return [two * q.x, two * q.y, two * q.z];
                }
                sin_half_angle.asin()
            } else {
                if sin_half_angle.is_zero() {
                    return [two * T::PI(), T::zero(), T::zero()];
                }
                let pi_minus_half_angle = sin_half_angle.asin();
                T::PI() - pi_minus_half_angle
            };
        let x = q.x / sin_half_angle;
        let y = q.y / sin_half_angle;
        let z = q.z / sin_half_angle;
        [x * angle, y * angle, z * angle]
    }
}
impl<T> UnitQuaternion<T>
where
    T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + One + Clone,
{
    #[inline]
    pub fn to_rotation_matrix3x3(self) -> [T; 9] {
        let two = T::one() + T::one();
        let Self(Quaternion { w, x, y, z }) = self;
        let wx = two.clone() * w.clone() * x.clone();
        let wy = two.clone() * w.clone() * y.clone();
        let wz = two.clone() * w * z.clone();
        let xx = two.clone() * x.clone() * x.clone();
        let xy = two.clone() * x.clone() * y.clone();
        let xz = two.clone() * x * z.clone();
        let yy = two.clone() * y.clone() * y.clone();
        let yz = two.clone() * y * z.clone();
        let zz = two * z.clone() * z;
        [
            T::one() - yy.clone() - zz.clone(),
            xy.clone() + wz.clone(),
            xz.clone() - wy.clone(),
            xy - wz,
            T::one() - xx.clone() - zz,
            yz.clone() + wx.clone(),
            xz + wy,
            yz - wx,
            T::one() - xx - yy,
        ]
    }
}
pub trait ReadMat3x3<T> {
    fn at(&self, row: usize, col: usize) -> &T;
}
impl<T> ReadMat3x3<T> for [T; 9] {
    fn at(&self, row: usize, col: usize) -> &T {
        &self[col + 3 * row]
    }
}
impl<T> ReadMat3x3<T> for [[T; 3]; 3] {
    fn at(&self, row: usize, col: usize) -> &T {
        &self[row][col]
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    pub fn from_rotation_matrix3x3(mat: &impl ReadMat3x3<T>) -> UnitQuaternion<T> {
        let zero = T::zero();
        let one = T::one();
        let two = one + one;
        let quarter = one / (two * two);
        let m00 = mat.at(0, 0);
        let m11 = mat.at(1, 1);
        let m22 = mat.at(2, 2);
        let trace: T = *m00 + *m11 + *m22;
        if trace > zero {
            let s = (trace + one).sqrt() * two; let qw = quarter * s;
            let qx = (*mat.at(1, 2) - *mat.at(2, 1)) / s;
            let qy = (*mat.at(2, 0) - *mat.at(0, 2)) / s;
            let qz = (*mat.at(0, 1) - *mat.at(1, 0)) / s;
            Self(Quaternion::new(qw, qx, qy, qz))
        } else if (m00 > m11) && (m00 > m22) {
            let s = (one + *m00 - *m11 - *m22).sqrt() * two; let qw = (*mat.at(1, 2) - *mat.at(2, 1)) / s;
            let qx = quarter * s;
            let qy = (*mat.at(1, 0) + *mat.at(0, 1)) / s;
            let qz = (*mat.at(2, 0) + *mat.at(0, 2)) / s;
            if *mat.at(1, 2) >= *mat.at(2, 1) {
                Self(Quaternion::new(qw, qx, qy, qz))
            } else {
                Self(Quaternion::new(-qw, -qx, -qy, -qz))
            }
        } else if m11 > m22 {
            let s = (one + *m11 - *m00 - *m22).sqrt() * two; let qw = (*mat.at(2, 0) - *mat.at(0, 2)) / s;
            let qx = (*mat.at(1, 0) + *mat.at(0, 1)) / s;
            let qy = quarter * s;
            let qz = (*mat.at(2, 1) + *mat.at(1, 2)) / s;
            if *mat.at(2, 0) >= *mat.at(0, 2) {
                Self(Quaternion::new(qw, qx, qy, qz))
            } else {
                Self(Quaternion::new(-qw, -qx, -qy, -qz))
            }
        } else {
            let s = (one + *m22 - *m00 - *m11).sqrt() * two; let qw = (*mat.at(0, 1) - *mat.at(1, 0)) / s;
            let qx = (*mat.at(2, 0) + *mat.at(0, 2)) / s;
            let qy = (*mat.at(2, 1) + *mat.at(1, 2)) / s;
            let qz = quarter * s;
            if *mat.at(0, 1) >= *mat.at(1, 0) {
                Self(Quaternion::new(qw, qx, qy, qz))
            } else {
                Self(Quaternion::new(-qw, -qx, -qy, -qz))
            }
        }
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    fn make_perpendicular_unit_vector(a: &[T; 3]) -> [T; 3] {
        let zero = T::zero();
        let a_sqr = [a[0] * a[0], a[1] * a[1], a[2] * a[2]];
        if a_sqr[0] <= a_sqr[1] {
            if a_sqr[0] <= a_sqr[2] {
                let norm = (a_sqr[1] + a_sqr[2]).sqrt();
                [zero, a[2] / norm, -a[1] / norm]
            } else {
                let norm = (a_sqr[0] + a_sqr[1]).sqrt();
                [a[1] / norm, -a[0] / norm, zero]
            }
        } else if a_sqr[1] <= a_sqr[2] {
            let norm = (a_sqr[0] + a_sqr[2]).sqrt();
            [a[2] / norm, zero, -a[0] / norm]
        } else {
            let norm = (a_sqr[0] + a_sqr[1]).sqrt();
            [a[1] / norm, -a[0] / norm, zero]
        }
    }
    pub fn from_two_vectors(a: &[T; 3], b: &[T; 3]) -> UnitQuaternion<T> {
        let zero = T::zero();
        let one = T::one();
        let two = one + one;
        let half = one / two;
        let a_norm = a[0].hypot(a[1].hypot(a[2]));
        let b_norm = b[0].hypot(b[1].hypot(b[2]));
        if a_norm.is_zero() || b_norm.is_zero() {
            return UnitQuaternion::one();
        }
        let a = [a[0] / a_norm, a[1] / a_norm, a[2] / a_norm];
        let b = [b[0] / b_norm, b[1] / b_norm, b[2] / b_norm];
        let v = [
            a[1] * b[2] - a[2] * b[1],
            a[2] * b[0] - a[0] * b[2],
            a[0] * b[1] - a[1] * b[0],
        ];
        let d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
        let wx2 = if d >= -half {
            (two * d + two).sqrt()
        } else {
            let v_norm_sqr = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
            if v_norm_sqr.is_zero() {
                let [x, y, z] = Self::make_perpendicular_unit_vector(&a);
                return UnitQuaternion(Quaternion::new(zero, x, y, z));
            }
            (v_norm_sqr / (half - d * half)).sqrt()
        };
        UnitQuaternion(Quaternion::new(
            wx2 / two,
            v[0] / wx2,
            v[1] / wx2,
            v[2] / wx2,
        ))
    }
}
impl<T> Default for UnitQuaternion<T>
where
    T: Num + Clone,
{
    #[inline]
    fn default() -> Self {
        Self::one()
    }
}
impl<T> UnitQuaternion<T>
where
    T: ConstZero + ConstOne,
{
    pub const ONE: Self = Self(Quaternion::ONE);
    pub const I: Self = Self(Quaternion::I);
    pub const J: Self = Self(Quaternion::J);
    pub const K: Self = Self(Quaternion::K);
}
impl<T> ConstOne for UnitQuaternion<T>
where
    T: ConstZero + ConstOne + Num + Clone,
{
    const ONE: Self = Self::ONE;
}
impl<T> One for UnitQuaternion<T>
where
    T: Num + Clone,
{
    #[inline]
    fn one() -> Self {
        Self(Quaternion::one())
    }
    #[inline]
    fn is_one(&self) -> bool {
        self.0.is_one()
    }
    #[inline]
    fn set_one(&mut self) {
        self.0.set_one();
    }
}
impl<T> UnitQuaternion<T>
where
    T: Zero + One,
{
    #[inline]
    pub fn i() -> Self {
        Self(Quaternion::i())
    }
    #[inline]
    pub fn j() -> Self {
        Self(Quaternion::j())
    }
    #[inline]
    pub fn k() -> Self {
        Self(Quaternion::k())
    }
}
impl<T> UnitQuaternion<T> {
    #[inline]
    pub fn into_quaternion(self) -> Quaternion<T> {
        self.0
    }
    #[inline]
    pub fn as_quaternion(&self) -> &Quaternion<T> {
        &self.0
    }
}
impl<T> Borrow<Quaternion<T>> for UnitQuaternion<T> {
    fn borrow(&self) -> &Quaternion<T> {
        self.as_quaternion()
    }
}
impl<T> UnitQuaternion<T>
where
    T: Clone + Neg<Output = T>,
{
    #[inline]
    pub fn conj(&self) -> Self {
        Self(self.0.conj())
    }
}
impl<T> UnitQuaternion<T>
where
    T: Clone + Neg<Output = T>,
{
    #[inline]
    pub fn inv(&self) -> Self {
        self.conj()
    }
}
impl<T> Inv for &UnitQuaternion<T>
where
    T: Clone + Neg<Output = T>,
{
    type Output = UnitQuaternion<T>;
    #[inline]
    fn inv(self) -> Self::Output {
        self.conj()
    }
}
impl<T> Inv for UnitQuaternion<T>
where
    T: Clone + Neg<Output = T>,
{
    type Output = UnitQuaternion<T>;
    #[inline]
    fn inv(self) -> Self::Output {
        self.conj()
    }
}
impl<T> Add<UnitQuaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Add<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self.0 + rhs.0
    }
}
impl<T> Add<Quaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Add<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: Quaternion<T>) -> Self::Output {
        self.0 + rhs
    }
}
impl<T> Add<T> for UnitQuaternion<T>
where
    Quaternion<T>: Add<T, Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn add(self, rhs: T) -> Self::Output {
        self.0 + rhs
    }
}
impl Add<UQ32> for f32 {
    type Output = Q32;
    #[inline]
    fn add(self, rhs: UQ32) -> Self::Output {
        self + rhs.0
    }
}
impl Add<UQ64> for f64 {
    type Output = Q64;
    #[inline]
    fn add(self, rhs: UQ64) -> Self::Output {
        self + rhs.0
    }
}
impl<T> Sub<UnitQuaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Sub<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self.0 - rhs.0
    }
}
impl<T> Sub<Quaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Sub<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: Quaternion<T>) -> Self::Output {
        self.0 - rhs
    }
}
impl<T> Sub<T> for UnitQuaternion<T>
where
    Quaternion<T>: Sub<T, Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn sub(self, rhs: T) -> Self::Output {
        self.0 - rhs
    }
}
impl Sub<UQ32> for f32 {
    type Output = Q32;
    #[inline]
    fn sub(self, rhs: UQ32) -> Self::Output {
        self - rhs.0
    }
}
impl Sub<UQ64> for f64 {
    type Output = Q64;
    #[inline]
    fn sub(self, rhs: UQ64) -> Self::Output {
        self - rhs.0
    }
}
impl<T> Mul<UnitQuaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Mul<Output = Quaternion<T>>,
{
    type Output = UnitQuaternion<T>;
    #[inline]
    fn mul(self, rhs: UnitQuaternion<T>) -> Self::Output {
        Self(self.0 * rhs.0)
    }
}
impl<T> Mul<Quaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Mul<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn mul(self, rhs: Quaternion<T>) -> Self::Output {
        self.0 * rhs
    }
}
impl<T> Mul<T> for UnitQuaternion<T>
where
    Quaternion<T>: Mul<T, Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn mul(self, rhs: T) -> Self::Output {
        self.0 * rhs
    }
}
impl Mul<UQ32> for f32 {
    type Output = Q32;
    #[inline]
    fn mul(self, rhs: UQ32) -> Self::Output {
        self * rhs.0
    }
}
impl Mul<UQ64> for f64 {
    type Output = Q64;
    #[inline]
    fn mul(self, rhs: UQ64) -> Self::Output {
        self * rhs.0
    }
}
impl<T> Div<UnitQuaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Mul<Output = Quaternion<T>>,
    T: Clone + Neg<Output = T>,
{
    type Output = UnitQuaternion<T>;
    #[allow(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn div(self, rhs: UnitQuaternion<T>) -> Self::Output {
        self * rhs.conj()
    }
}
impl<T> Div<Quaternion<T>> for UnitQuaternion<T>
where
    Quaternion<T>: Div<Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn div(self, rhs: Quaternion<T>) -> Self::Output {
        self.0 / rhs
    }
}
impl<T> Div<T> for UnitQuaternion<T>
where
    Quaternion<T>: Div<T, Output = Quaternion<T>>,
{
    type Output = Quaternion<T>;
    #[inline]
    fn div(self, rhs: T) -> Self::Output {
        self.0 / rhs
    }
}
impl Div<UQ32> for f32 {
    type Output = Q32;
    #[allow(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn div(self, rhs: UQ32) -> Self::Output {
        self * rhs.inv().0
    }
}
impl Div<UQ64> for f64 {
    type Output = Q64;
    #[allow(clippy::suspicious_arithmetic_impl)]
    #[inline]
    fn div(self, rhs: UQ64) -> Self::Output {
        self * rhs.inv().0
    }
}
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, T);
impl<T> Neg for UnitQuaternion<T>
where
    T: Neg<Output = T>,
{
    type Output = UnitQuaternion<T>;
    fn neg(self) -> Self::Output {
        Self(-self.0)
    }
}
impl<T> UnitQuaternion<T>
where
    T: Add<T, Output = T> + Mul<T, Output = T>,
{
    #[inline]
    pub fn dot(self, other: Self) -> T {
        self.0.dot(other.0)
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    #[inline]
    pub fn adjust_norm(self) -> Self {
        self.0
            .normalize()
            .expect("Unit quaternion value too inaccurate. Cannot renormalize.")
    }
}
impl<T> UnitQuaternion<T>
where
    T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone,
{
    pub fn rotate_vector(self, vector: [T; 3]) -> [T; 3] {
        let q = self.into_quaternion();
        let [vx, vy, vz] = vector;
        let q_inv_v = Quaternion::<T>::new(
            q.x.clone() * vx.clone() + q.y.clone() * vy.clone() + q.z.clone() * vz.clone(),
            q.w.clone() * vx.clone() - q.y.clone() * vz.clone() + q.z.clone() * vy.clone(),
            q.w.clone() * vy.clone() + q.x.clone() * vz.clone() - q.z.clone() * vx.clone(),
            q.w.clone() * vz - q.x.clone() * vy + q.y.clone() * vx,
        );
        let result = q_inv_v * q;
        [result.x, result.y, result.z]
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float,
{
    pub fn slerp(&self, other: &Self, t: T) -> Self {
        let one = T::one();
        let dot = self.dot(*other);
        let (dot, other) = if dot.is_sign_positive() {
            (dot, *other)
        } else {
            (-dot, -*other)
        };
        let threshold = one - T::epsilon().sqrt();
        if dot > threshold {
            return Self(*self + (other - *self) * t);
        }
        let theta_0 = dot.acos();
        let theta = theta_0 * t;
        let sin_theta = theta.sin();
        let sin_theta_0 = theta_0.sin();
        let s0 = ((one - t) * theta_0).sin() / sin_theta_0;
        let s1 = sin_theta / sin_theta_0;
        Self(*self * s0 + other * s1)
    }
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
    T: Float + FloatConst,
{
    pub fn sqrt(self) -> Self {
        let zero = T::zero();
        let one = T::one();
        let two = one + one;
        let half = one / two;
        let UnitQuaternion(c) = self;
        if c.w >= -half {
            let wx2 = (c.w * two + two).sqrt();
            UnitQuaternion(Quaternion::new(wx2 * half, c.x / wx2, c.y / wx2, c.z / wx2))
        } else {
            let im_norm_sqr = c.y * c.y + (c.x * c.x + c.z * c.z);
            if im_norm_sqr >= T::min_positive_value() {
                let wx2 = (im_norm_sqr * two / (one - c.w)).sqrt();
                UnitQuaternion(Quaternion::new(wx2 * half, c.x / wx2, c.y / wx2, c.z / wx2))
            } else if c.x.is_zero() && c.y.is_zero() && c.z.is_zero() {
                UnitQuaternion(Quaternion::new(zero, one.copysign(c.x), c.y, c.z))
            } else {
                let s = one / T::min_positive_value();
                let sx = s * c.x;
                let sy = s * c.y;
                let sz = s * c.z;
                let im_norm = (sy * sy + (sx * sx + sz * sz)).sqrt() / s;
                UnitQuaternion(Quaternion::new(
                    im_norm * half,
                    c.x / im_norm,
                    c.y / im_norm,
                    c.z / im_norm,
                ))
            }
        }
    }
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for UnitQuaternion<T>
where
    T: serde::Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for UnitQuaternion<T>
where
    T: serde::Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let q = serde::Deserialize::deserialize(deserializer)?;
        Ok(Self(q))
    }
}
#[cfg(test)]
mod tests {
    #[cfg(feature = "std")]
    use core::hash::Hash;
    #[cfg(feature = "std")]
    use core::hash::Hasher;
    #[cfg(feature = "std")]
    use std::collections::hash_map::DefaultHasher;
    use num_traits::ConstOne;
    use num_traits::ConstZero;
    use num_traits::FloatConst;
    use num_traits::Inv;
    use num_traits::One;
    use num_traits::Zero;
    #[cfg(any(feature = "std", feature = "libm", feature = "serde"))]
    use crate::EulerAngles;
    use crate::Quaternion;
    use crate::UnitQuaternion;
    use crate::Q32;
    use crate::Q64;
    use crate::UQ32;
    use crate::UQ64;
    #[test]
    fn test_new() {
        let q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(q.w, 1.0);
        assert_eq!(q.x, 2.0);
        assert_eq!(q.y, 3.0);
        assert_eq!(q.z, 4.0);
    }
    #[test]
    fn test_zero_constant() {
        let q1 = Q32::ZERO;
        let q2 = Q32::default();
        assert_eq!(q1, q2);
    }
    #[test]
    fn test_const_zero_trait() {
        let q3 = <Q64 as ConstZero>::ZERO;
        let q4 = Q64::default();
        assert_eq!(q3, q4);
    }
    #[test]
    fn test_zero_trait() {
        let q1 = Q32::zero();
        let q2 = Q32::default();
        assert_eq!(q1, q2);
        assert!(q1.is_zero());
    }
    #[test]
    fn test_zero_trait_set_zero() {
        let mut q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        q.set_zero();
        assert!(q.is_zero());
    }
    #[test]
    fn test_one_constant() {
        assert_eq!(Q32::ONE, Q32::new(1.0, 0.0, 0.0, 0.0))
    }
    #[test]
    fn test_i_constant() {
        assert_eq!(Q64::I, Q64::new(0.0, 1.0, 0.0, 0.0))
    }
    #[test]
    fn test_j_constant() {
        assert_eq!(Q32::J, Q32::new(0.0, 0.0, 1.0, 0.0))
    }
    #[test]
    fn test_k_constant() {
        assert_eq!(Q64::K, Q64::new(0.0, 0.0, 0.0, 1.0))
    }
    #[test]
    fn test_const_one_trait() {
        assert_eq!(<Q32 as ConstOne>::ONE, Q32::new(1.0, 0.0, 0.0, 0.0))
    }
    #[test]
    fn test_one_trait_one() {
        assert_eq!(<Q64 as One>::one(), Q64::ONE);
        assert!(Q64::ONE.is_one());
    }
    #[test]
    fn test_one_trait_set_one() {
        let mut q = Q32::new(2.0, 3.0, 4.0, 5.0);
        q.set_one();
        assert!(q.is_one());
    }
    #[test]
    fn test_i_func() {
        assert_eq!(Q64::i(), Q64::new(0.0, 1.0, 0.0, 0.0));
    }
    #[test]
    fn test_j_func() {
        assert_eq!(Q64::j(), Q64::new(0.0, 0.0, 1.0, 0.0));
    }
    #[test]
    fn test_k_func() {
        assert_eq!(Q64::k(), Q64::new(0.0, 0.0, 0.0, 1.0));
    }
    #[test]
    fn test_norm_sqr() {
        assert_eq!(Q32::ZERO.norm_sqr(), 0.0);
        assert_eq!(Q64::ONE.norm_sqr(), 1.0);
        assert_eq!(Q32::I.norm_sqr(), 1.0);
        assert_eq!(Q64::J.norm_sqr(), 1.0);
        assert_eq!(Q32::K.norm_sqr(), 1.0);
        assert_eq!(Q64::new(-8.0, 4.0, 2.0, -1.0).norm_sqr(), 85.0);
        assert_eq!(Q32::new(1.0, -2.0, 3.0, -4.0).norm_sqr(), 30.0);
    }
    #[test]
    fn test_conj() {
        assert_eq!(Q64::ONE.conj(), Q64::ONE);
        assert_eq!(Q32::I.conj(), -Q32::I);
        assert_eq!(Q64::J.conj(), -Q64::J);
        assert_eq!(Q32::K.conj(), -Q32::K);
        assert_eq!(
            Q64::new(-8.0, 4.0, 2.0, -1.0).conj(),
            Q64::new(-8.0, -4.0, -2.0, 1.0)
        );
        assert_eq!(
            Q32::new(1.0, -2.0, 3.0, -4.0).conj(),
            Q32::new(1.0, 2.0, -3.0, 4.0)
        );
    }
    #[test]
    fn test_inv_func() {
        assert_eq!(Q64::ONE.inv(), Q64::ONE);
        assert_eq!(Q32::I.inv(), -Q32::I);
        assert_eq!(Q64::J.inv(), -Q64::J);
        assert_eq!(Q32::K.inv(), -Q32::K);
        assert_eq!(
            Q64::new(1.0, 1.0, -1.0, -1.0).inv(),
            Q64::new(0.25, -0.25, 0.25, 0.25)
        );
        assert_eq!(
            Q32::new(1.0, -2.0, 2.0, -4.0).inv(),
            Q32::new(0.04, 0.08, -0.08, 0.16)
        );
    }
    #[test]
    fn test_inv_trait_for_ref() {
        assert_eq!(Inv::inv(&Q64::ONE), Q64::ONE);
        assert_eq!(Inv::inv(&Q32::I), -Q32::I);
        assert_eq!(Inv::inv(&Q64::J), -Q64::J);
        assert_eq!(Inv::inv(&Q32::K), -Q32::K);
        assert_eq!(
            Inv::inv(&Q64::new(1.0, 1.0, -1.0, -1.0)),
            Q64::new(0.25, -0.25, 0.25, 0.25)
        );
        assert_eq!(
            Inv::inv(&Q32::new(1.0, -2.0, 2.0, -4.0)),
            Q32::new(0.04, 0.08, -0.08, 0.16)
        );
    }
    #[test]
    fn test_inv_trait_for_val() {
        assert_eq!(Inv::inv(Q64::ONE), Q64::ONE);
        assert_eq!(Inv::inv(Q32::I), -Q32::I);
        assert_eq!(Inv::inv(Q64::J), -Q64::J);
        assert_eq!(Inv::inv(Q32::K), -Q32::K);
        assert_eq!(
            Inv::inv(Q64::new(1.0, 1.0, -1.0, -1.0)),
            Q64::new(0.25, -0.25, 0.25, 0.25)
        );
        assert_eq!(
            Inv::inv(Q32::new(1.0, -2.0, 2.0, -4.0)),
            Q32::new(0.04, 0.08, -0.08, 0.16)
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_norm_normal_values() {
        let q = Q64::new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(q.norm(), 30.0f64.sqrt());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_norm_zero_quaternion() {
        let q = Q32::new(0.0, 0.0, 0.0, 0.0);
        assert_eq!(q.norm(), 0.0, "Norm of zero quaternion should be 0");
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_norm_subnormal_values() {
        let s = f64::MIN_POSITIVE;
        let q = Q64::new(s, s, s, s);
        assert!(
            (q.norm() - 2.0 * s).abs() <= 2.0 * s * f64::EPSILON,
            "Norm of subnormal is computed correctly"
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_norm_infinite_values() {
        let inf = f32::INFINITY;
        assert_eq!(Q32::new(inf, 1.0, 1.0, 1.0).norm(), inf);
        assert_eq!(Q32::new(1.0, inf, 1.0, 1.0).norm(), inf);
        assert_eq!(Q32::new(1.0, 1.0, inf, 1.0).norm(), inf);
        assert_eq!(Q32::new(1.0, 1.0, 1.0, inf).norm(), inf);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_norm_nan_values() {
        let nan = f32::NAN;
        assert!(Q32::new(nan, 1.0, 1.0, 1.0).norm().is_nan());
        assert!(Q32::new(1.0, nan, 1.0, 1.0).norm().is_nan());
        assert!(Q32::new(1.0, 1.0, nan, 1.0).norm().is_nan());
        assert!(Q32::new(1.0, 1.0, 1.0, nan).norm().is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_normal_values() {
        let q = Q64 {
            w: 1.1,
            x: 2.7,
            y: 3.4,
            z: 4.9,
        };
        assert_eq!(
            q.fast_norm(),
            q.norm(),
            "Fast norm is equal to norm for normal floating point values"
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_zero_quaternion() {
        assert_eq!(
            Q32::zero().fast_norm(),
            0.0,
            "Fast norm of zero quaternion should be 0"
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_infinite_values() {
        let inf = f32::INFINITY;
        assert_eq!(Q32::new(inf, 1.0, 1.0, 1.0).fast_norm(), inf);
        assert_eq!(Q32::new(1.0, inf, 1.0, 1.0).fast_norm(), inf);
        assert_eq!(Q32::new(1.0, 1.0, inf, 1.0).fast_norm(), inf);
        assert_eq!(Q32::new(1.0, 1.0, 1.0, inf).fast_norm(), inf);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_nan_values() {
        let nan = f32::NAN;
        assert!(Q32::new(nan, 1.0, 1.0, 1.0).fast_norm().is_nan());
        assert!(Q32::new(1.0, nan, 1.0, 1.0).fast_norm().is_nan());
        assert!(Q32::new(1.0, 1.0, nan, 1.0).fast_norm().is_nan());
        assert!(Q32::new(1.0, 1.0, 1.0, nan).fast_norm().is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_for_norm_sqr_underflow() {
        let s = f64::MIN_POSITIVE;
        let q = Q64::new(s, s, s, s);
        assert_eq!(q.fast_norm(), 0.0);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_fast_norm_for_norm_sqr_overflow() {
        let s = f32::MAX / 16.0;
        let q = Q32::new(s, s, s, s);
        assert_eq!(q.fast_norm(), f32::INFINITY);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_normalize() {
        assert_eq!(Q64::ONE.normalize().unwrap(), UQ64::ONE);
        assert_eq!(Q32::I.normalize().unwrap(), UQ32::I);
        assert_eq!(Q64::J.normalize().unwrap(), UQ64::J);
        assert_eq!(Q32::K.normalize().unwrap(), UQ32::K);
        assert_eq!(
            Q64::new(9.0, 12.0, -12.0, -16.0)
                .normalize()
                .unwrap()
                .into_quaternion(),
            Q64::new(0.36, 0.48, -0.48, -0.64)
        );
        assert_eq!(
            Q32::new(-1.0, -1.0, 1.0, -1.0)
                .normalize()
                .unwrap()
                .into_quaternion(),
            Q32::new(-0.5, -0.5, 0.5, -0.5)
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_normalize_zero_infinity_nan() {
        assert_eq!(Q64::ZERO.normalize(), None);
        assert_eq!(Q64::new(f64::INFINITY, 0.0, 0.0, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, f64::NEG_INFINITY, 0.0, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, 0.0, f64::NEG_INFINITY, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, 0.0, 0.0, f64::INFINITY).normalize(), None);
        assert_eq!(Q64::new(f64::NAN, 0.0, 0.0, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, f64::NAN, 0.0, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, 0.0, f64::NAN, 0.0).normalize(), None);
        assert_eq!(Q64::new(0.0, 0.0, 0.0, f64::NAN).normalize(), None);
        assert_eq!(
            Q64::new(f64::INFINITY, f64::NAN, 1.0, 0.0).normalize(),
            None
        );
        assert_eq!(
            Q64::new(1.0, 0.0, f64::INFINITY, f64::NAN).normalize(),
            None
        );
    }
    #[test]
    fn test_from_underlying_type_val() {
        assert_eq!(Q64::from(-5.0), Q64::new(-5.0, 0.0, 0.0, 0.0));
        assert_eq!(Into::<Q32>::into(42.0), Q32::new(42.0, 0.0, 0.0, 0.0));
    }
    #[allow(clippy::needless_borrows_for_generic_args)]
    #[test]
    fn test_from_underlying_type_ref() {
        assert_eq!(Q64::from(&-5.0), Q64::new(-5.0, 0.0, 0.0, 0.0));
        assert_eq!(Into::<Q32>::into(&42.0), Q32::new(42.0, 0.0, 0.0, 0.0));
    }
    #[test]
    fn test_from_unit_quaternion() {
        assert_eq!(Q32::from(UQ32::ONE), Q32::ONE);
        assert_eq!(Q64::from(UQ64::I), Q64::I);
        assert_eq!(Q32::from(UQ32::J), Q32::J);
        assert_eq!(Q64::from(UQ64::K), Q64::K);
    }
    #[allow(clippy::needless_borrows_for_generic_args)]
    #[test]
    fn test_from_unit_quaternion_ref() {
        assert_eq!(<&Q32 as From<&UQ32>>::from(&UQ32::ONE), &Q32::ONE);
        assert_eq!(<&Q64 as From<&UQ64>>::from(&UQ64::I), &Q64::I);
        assert_eq!(<&Q32 as From<&UQ32>>::from(&UQ32::J), &Q32::J);
        assert_eq!(<&Q64 as From<&UQ64>>::from(&UQ64::K), &Q64::K);
    }
    #[test]
    fn test_add_quaternion() {
        assert_eq!(Q32::ONE + Q32::J, Q32::new(1.0, 0.0, 1.0, 0.0));
        assert_eq!(
            Q64::new(1.0, 2.0, 3.0, 4.0) + Q64::new(1.0, 3.0, 10.0, -5.0),
            Q64::new(2.0, 5.0, 13.0, -1.0)
        );
    }
    #[test]
    fn test_add_real() {
        assert_eq!(Q32::I + 1.0, Q32::new(1.0, 1.0, 0.0, 0.0));
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) + 42.0,
            Q32::new(43.0, 2.0, 3.0, 4.0)
        );
    }
    #[test]
    fn test_sub_quaternion() {
        assert_eq!(Q32::ONE - Q32::J, Q32::new(1.0, 0.0, -1.0, 0.0));
        assert_eq!(
            Q64::new(1.0, 2.0, 3.0, 4.0) - Q64::new(1.0, 3.0, 10.0, -5.0),
            Q64::new(0.0, -1.0, -7.0, 9.0)
        );
    }
    #[test]
    fn test_sub_real() {
        assert_eq!(Q32::I - 1.0, Q32::new(-1.0, 1.0, 0.0, 0.0));
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) - 42.0,
            Q32::new(-41.0, 2.0, 3.0, 4.0)
        );
    }
    #[test]
    fn test_mul_quaternion() {
        assert_eq!(Q32::ONE * Q32::ONE, Q32::ONE);
        assert_eq!(Q32::ONE * Q32::I, Q32::I);
        assert_eq!(Q32::ONE * Q32::J, Q32::J);
        assert_eq!(Q32::ONE * Q32::K, Q32::K);
        assert_eq!(Q32::I * Q32::ONE, Q32::I);
        assert_eq!(Q32::J * Q32::ONE, Q32::J);
        assert_eq!(Q32::K * Q32::ONE, Q32::K);
        assert_eq!(Q32::I * Q32::I, -Q32::ONE);
        assert_eq!(Q32::J * Q32::J, -Q32::ONE);
        assert_eq!(Q32::K * Q32::K, -Q32::ONE);
        assert_eq!(Q32::I * Q32::J, Q32::K);
        assert_eq!(Q32::J * Q32::K, Q32::I);
        assert_eq!(Q32::K * Q32::I, Q32::J);
        assert_eq!(Q32::J * Q32::I, -Q32::K);
        assert_eq!(Q32::K * Q32::J, -Q32::I);
        assert_eq!(Q32::I * Q32::K, -Q32::J);
        assert_eq!(
            Q64::new(1.0, 2.0, 3.0, 4.0) * Q64::new(1.0, 3.0, 10.0, -5.0),
            Q64::new(-15.0, -50.0, 35.0, 10.0)
        );
    }
    #[test]
    fn test_mul_real() {
        assert_eq!(Q32::I * 1.0, Q32::I);
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) * 42.0,
            Q32::new(42.0, 84.0, 126.0, 168.0)
        );
    }
    #[test]
    fn test_mul_quaternion_by_unit_quaternion() {
        assert_eq!(Q32::I * UQ32::J, Q32::K);
        assert_eq!(Q64::J * UQ64::K, Q64::I);
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) * UQ32::K,
            Q32::new(-4.0, 3.0, -2.0, 1.0)
        );
    }
    #[test]
    fn test_div_quaternion() {
        assert_eq!(Q32::ONE / Q32::ONE * Q32::ONE, Q32::ONE);
        assert_eq!(Q32::ONE / Q32::I * Q32::I, Q32::ONE);
        assert_eq!(Q32::ONE / Q32::J * Q32::J, Q32::ONE);
        assert_eq!(Q32::ONE / Q32::K * Q32::K, Q32::ONE);
        assert_eq!(Q32::I / Q32::ONE * Q32::ONE, Q32::I);
        assert_eq!(Q32::J / Q32::ONE * Q32::ONE, Q32::J);
        assert_eq!(Q32::K / Q32::ONE * Q32::ONE, Q32::K);
        assert_eq!(Q32::I / Q32::I * Q32::I, Q32::I);
        assert_eq!(Q32::J / Q32::J * Q32::J, Q32::J);
        assert_eq!(Q32::K / Q32::K * Q32::K, Q32::K);
        assert_eq!(Q32::I / Q32::J * Q32::J, Q32::I);
        assert_eq!(Q32::J / Q32::K * Q32::K, Q32::J);
        assert_eq!(Q32::K / Q32::I * Q32::I, Q32::K);
        assert_eq!(Q32::J / Q32::I * Q32::I, Q32::J);
        assert_eq!(Q32::K / Q32::J * Q32::J, Q32::K);
        assert_eq!(Q32::I / Q32::K * Q32::K, Q32::I);
        let q = Q64::new(1.0, 2.0, 3.0, 4.0);
        let r = Q64::new(1.0, 3.0, 10.0, -5.0);
        assert!((q / r * r - q).norm_sqr() < f64::EPSILON);
    }
    #[test]
    fn test_div_real() {
        assert_eq!(Q32::I * 1.0, Q32::I);
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) / 4.0,
            Q32::new(0.25, 0.5, 0.75, 1.0)
        );
    }
    #[test]
    fn test_div_quaternion_by_unit_quaternion() {
        assert_eq!(Q32::I / UQ32::J, -Q32::K);
        assert_eq!(Q64::J / UQ64::K, -Q64::I);
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0) / UQ32::K,
            Q32::new(4.0, -3.0, 2.0, -1.0)
        );
    }
    #[test]
    fn test_add_assign() {
        let mut q = Q32::new(1.0, 2.0, 3.0, 4.0);
        q += 4.0;
        assert_eq!(q, Quaternion::new(5.0, 2.0, 3.0, 4.0));
    }
    #[test]
    fn test_sub_assign() {
        let mut q = Q64::new(1.0, 2.0, 3.0, 4.0);
        q -= Q64::new(4.0, 8.0, 6.0, 1.0);
        assert_eq!(q, Quaternion::new(-3.0, -6.0, -3.0, 3.0));
    }
    #[test]
    fn test_mul_assign() {
        let mut q = Q32::new(1.0, 2.0, 3.0, 4.0);
        q *= Q32::I;
        assert_eq!(q, Quaternion::new(-2.0, 1.0, 4.0, -3.0));
    }
    #[test]
    fn test_div_assign() {
        let mut q = Quaternion::new(1.0f32, 2.0f32, 3.0f32, 4.0f32);
        q /= 4.0f32;
        assert_eq!(q, Quaternion::new(0.25f32, 0.5f32, 0.75f32, 1.0f32));
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_add_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_sub_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_mul_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_div_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_add_unit_quaternion_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_sub_unit_quaternion_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_mul_unit_quaternion_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_div_unit_quaternion_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_add_real_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = 5.0;
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_sub_real_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = 5.0;
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_mul_real_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = 5.0;
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[allow(clippy::op_ref)]
    fn test_div_real_with_ref() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let rhs = 5.0;
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    fn test_add_lhs_real() {
        assert_eq!(42.0 + Quaternion::I, Quaternion::new(42.0, 1.0, 0.0, 0.0));
        assert_eq!(1 + Quaternion::new(2, 4, 6, 8), Quaternion::new(3, 4, 6, 8));
    }
    #[test]
    fn test_sub_lhs_real() {
        assert_eq!(42.0 - Quaternion::I, Quaternion::new(42.0, -1.0, 0.0, 0.0));
        assert_eq!(
            1 - Quaternion::new(2, 4, 6, 8),
            Quaternion::new(-1, -4, -6, -8)
        );
    }
    #[test]
    fn test_mul_lhs_real() {
        assert_eq!(42.0 * Quaternion::I, Quaternion::new(0.0, 42.0, 0.0, 0.0));
        assert_eq!(2 * Quaternion::new(1, 2, 3, 4), Quaternion::new(2, 4, 6, 8));
    }
    #[test]
    fn test_div_lhs_real() {
        assert_eq!(
            42.0f32 / Quaternion::I,
            Quaternion::new(0.0, -42.0, 0.0, 0.0)
        );
        assert_eq!(
            4.0f64 / Quaternion::new(1.0, 1.0, 1.0, 1.0),
            Quaternion::new(1.0, -1.0, -1.0, -1.0)
        );
    }
    #[test]
    fn test_neg() {
        assert_eq!(
            -Q64::new(1.0, -2.0, 3.0, -4.0),
            Q64::new(-1.0, 2.0, -3.0, 4.0)
        );
    }
    #[test]
    fn test_powu() {
        for q in [
            Q32::ONE,
            Q32::ZERO,
            Q32::I,
            Q32::new(1.0, 1.0, 1.0, 1.0),
            Q32::new(1.0, 2.0, -3.0, 4.0),
        ] {
            let mut expected = Q32::ONE;
            for e in 0..16 {
                assert_eq!(q.powu(e), expected);
                expected *= q;
            }
        }
    }
    #[test]
    fn test_powi() {
        for q in [
            Q32::ONE,
            Q32::I,
            Q32::new(1.0, 1.0, 1.0, 1.0),
            Q32::new(1.0, 2.0, -3.0, 4.0),
        ] {
            let mut expected = Q32::ONE;
            for e in 0..16 {
                assert_eq!(q.powi(e), expected);
                assert!(
                    (q.powi(-e) - expected.inv()).norm_sqr() / expected.norm_sqr() < f32::EPSILON
                );
                expected *= q;
            }
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_zero_quaternion() {
        assert_eq!(Q32::ZERO.exp(), Q32::ONE);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_real_part_only() {
        assert_eq!(Q32::ONE.exp(), core::f32::consts::E.into())
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_imaginary_part_only() {
        assert!(
            (Q64::I.exp() - Q64::new(1.0f64.cos(), 1.0f64.sin(), 0.0, 0.0)).norm() <= f64::EPSILON
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_complex_quaternion() {
        let q = Q32::new(1.0, 1.0, 1.0, 1.0);
        let exp_q = q.exp();
        let expected_norm = 1.0f32.exp();
        let angle = 3.0f32.sqrt();
        assert!(
            (exp_q
                - Q32::new(
                    expected_norm * angle.cos(),
                    expected_norm * angle.sin() / angle,
                    expected_norm * angle.sin() / angle,
                    expected_norm * angle.sin() / angle
                ))
            .norm()
                <= 2.0 * expected_norm * f32::EPSILON
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_negative_real_part() {
        let q = Q64::new(-1000.0, 0.0, f64::INFINITY, f64::NAN);
        let exp_q = q.exp();
        assert_eq!(exp_q, Q64::zero());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_nan_input() {
        let q = Q32::new(f32::NAN, 1.0, 1.0, 1.0);
        let exp_q = q.exp();
        assert!(exp_q.w.is_nan());
        assert!(exp_q.x.is_nan());
        assert!(exp_q.y.is_nan());
        assert!(exp_q.z.is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_large_imaginary_norm() {
        let q = Q32::new(1.0, 1e30, 1e30, 1e30);
        let exp_q = q.exp();
        assert!(exp_q.w.is_nan());
        assert!(exp_q.x.is_nan());
        assert!(exp_q.y.is_nan());
        assert!(exp_q.z.is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_infinite_real_part() {
        let inf = f64::INFINITY;
        let q = Quaternion::new(inf, 1.0, 1.0, 1.0);
        let exp_q = q.exp();
        assert_eq!(exp_q.w, -inf);
        assert_eq!(exp_q.x, inf);
        assert_eq!(exp_q.y, inf);
        assert_eq!(exp_q.z, inf);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_infinite_imaginary_part() {
        let q = Q32::new(1.0, f32::INFINITY, 0.0, 0.0);
        let exp_q = q.exp();
        assert!(exp_q.w.is_nan());
        assert!(exp_q.x.is_nan());
        assert!(exp_q.y.is_nan());
        assert!(exp_q.z.is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_small_imaginary_norm() {
        let epsilon = f32::EPSILON;
        let q = Quaternion::new(0.5, epsilon, epsilon, epsilon);
        let exp_q = q.exp();
        let result_norm = q.w.exp();
        let expected_exp_q = Quaternion::new(
            result_norm,
            result_norm * q.x,
            result_norm * q.y,
            result_norm * q.z,
        );
        assert!((exp_q - expected_exp_q).norm() <= epsilon);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_infinite_result_angle_greater_than_90_degrees() {
        let angle = f32::PI() * 0.75; let q = Q32::new(f32::INFINITY, angle, 0.0, 0.0);
        let exp_q = q.exp();
        assert_eq!(exp_q.w, -f32::INFINITY);
        assert_eq!(exp_q.x, f32::INFINITY);
        assert_eq!(exp_q.y, 0.0);
        assert_eq!(exp_q.z, 0.0);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_exp_infinite_result_angle_greater_than_180_degrees() {
        let angle = f64::PI() * 1.25; let q = Q64::new(f64::INFINITY, 0.0, angle, 0.0);
        let exp_q = q.exp();
        assert_eq!(exp_q.w, -f64::INFINITY);
        assert_eq!(exp_q.x, 0.0);
        assert_eq!(exp_q.y, -f64::INFINITY);
        assert_eq!(exp_q.z, 0.0);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_normal_case() {
        let q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
        let ln_q = q.ln();
        assert!((q.w - 30.0f64.ln() / 2.0) <= 4.0 * f64::EPSILON);
        assert!((ln_q.z / ln_q.x - q.z / q.x) <= 2.0 * f64::EPSILON);
        assert!((ln_q.y / ln_q.x - q.y / q.x) <= 2.0 * f64::EPSILON);
        assert!((ln_q.x.hypot(ln_q.y.hypot(ln_q.z)) - 29.0f64.sqrt().atan()) <= 4.0 * f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_positive_real_axis() {
        let q = Quaternion::new(1.0, 1e-10, 1e-10, 1e-10);
        let ln_q = q.ln();
        let expected = Quaternion::new(0.0, 1e-10, 1e-10, 1e-10); assert!((ln_q - expected).norm() <= 1e-11);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_negative_real_axis() {
        let q = Q32::new(-1.0, 0.0, 0.0, 0.0);
        let ln_q = q.ln();
        let expected = Q32::new(0.0, core::f32::consts::PI, 0.0, 0.0); assert!((ln_q - expected).norm() <= core::f32::consts::PI * f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_zero() {
        let q = Q32::new(0.0, 0.0, 0.0, 0.0);
        let ln_q = q.ln();
        let expected = f32::NEG_INFINITY.into();
        assert_eq!(ln_q, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_negative_zero() {
        let q = Q64::new(-0.0, 0.0, 0.0, 0.0);
        let ln_q = q.ln();
        let expected = Q64::new(f64::NEG_INFINITY, core::f64::consts::PI, 0.0, 0.0);
        assert_eq!(ln_q.w, expected.w);
        assert!((ln_q.x - expected.x).abs() <= core::f64::consts::PI * f64::EPSILON);
        assert_eq!(ln_q.y, expected.y);
        assert_eq!(ln_q.z, expected.z);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_nan() {
        let q = Quaternion::new(f32::NAN, 1.0, 1.0, 1.0);
        let ln_q = q.ln();
        assert!(ln_q.w.is_nan());
        assert!(ln_q.x.is_nan());
        assert!(ln_q.y.is_nan());
        assert!(ln_q.z.is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_infinite() {
        let q = Q32::new(f32::INFINITY, 1.0, 1.0, 1.0);
        let ln_q = q.ln();
        let expected = Quaternion::new(f32::INFINITY, 0.0, 0.0, 0.0); assert_eq!(ln_q, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_ln_finite_and_infinite() {
        use num_traits::Signed;
        let q = Quaternion::new(1.0, f32::INFINITY, -f32::INFINITY, -1.0);
        let ln_q = q.ln();
        let expected = Quaternion::new(
            f32::INFINITY,
            core::f32::consts::PI / 8.0f32.sqrt(),
            -core::f32::consts::PI / 8.0f32.sqrt(),
            0.0,
        );
        assert_eq!(ln_q.w, expected.w);
        assert!((ln_q.x - expected.x).abs() <= 4.0f32 * f32::EPSILON);
        assert!((ln_q.y - expected.y).abs() <= 4.0f32 * f32::EPSILON);
        assert_eq!(ln_q.z, 0.0);
        assert!(ln_q.z.is_negative());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_normal() {
        let q = Q64::new(1.0, 2.0, 3.0, 4.0);
        assert!(((q * q).sqrt() - q).norm() <= q.norm() * f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_zero() {
        assert_eq!(Q32::ZERO.sqrt(), Q32::ZERO);
        let zero = Q32::new(-0.0, 0.0, -0.0, -0.0);
        assert!(zero.sqrt().w.is_sign_positive());
        assert!(zero.sqrt().x.is_sign_positive());
        assert!(zero.sqrt().y.is_sign_negative());
        assert!(zero.sqrt().z.is_sign_negative());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_negative_real() {
        let q = Q64::new(-4.0, -0.0, 0.0, 0.0);
        let sqrt_q = q.sqrt();
        let expected = Q64::new(0.0, -2.0, 0.0, 0.0);
        assert_eq!(sqrt_q, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_nan() {
        let q = Q32::new(f32::NAN, 0.0, 0.0, 0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w.is_nan());
        assert!(sqrt_q.x.is_nan());
        assert!(sqrt_q.y.is_nan());
        assert!(sqrt_q.z.is_nan());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_infinity() {
        let q = Q64::new(f64::INFINITY, -0.0, -0.0, 0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w.is_infinite());
        assert!(sqrt_q.x.is_zero());
        assert!(sqrt_q.y.is_zero());
        assert!(sqrt_q.z.is_zero());
        assert!(sqrt_q.w.is_sign_positive());
        assert!(sqrt_q.x.is_sign_negative());
        assert!(sqrt_q.y.is_sign_negative());
        assert!(sqrt_q.z.is_sign_positive());
        let q = Q32::new(0.0, 0.0, -f32::INFINITY, -0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w.is_infinite());
        assert!(sqrt_q.x.is_zero());
        assert!(sqrt_q.y.is_infinite());
        assert!(sqrt_q.z.is_zero());
        assert!(sqrt_q.w.is_sign_positive());
        assert!(sqrt_q.x.is_sign_positive());
        assert!(sqrt_q.y.is_sign_negative());
        assert!(sqrt_q.z.is_sign_negative());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_negative_infinity_real() {
        let q = Quaternion::new(-f64::INFINITY, 0.0, -1.0, 0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w.is_zero());
        assert!(sqrt_q.x.is_infinite());
        assert!(sqrt_q.y.is_zero());
        assert!(sqrt_q.z.is_zero());
        assert!(sqrt_q.w.is_sign_positive());
        assert!(sqrt_q.x.is_sign_positive());
        assert!(sqrt_q.y.is_sign_negative());
        assert!(sqrt_q.z.is_sign_positive());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_commutativity_with_conjugate() {
        let q = Q32::new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(q.conj().sqrt(), q.sqrt().conj());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_subnormal_values() {
        let subnormal = f64::MIN_POSITIVE / 2.0;
        let q = Quaternion::new(subnormal, subnormal, subnormal, subnormal);
        let sqrt_q = q.sqrt();
        let norm_sqr = sqrt_q.norm_sqr();
        assert!((norm_sqr - f64::MIN_POSITIVE).abs() <= 4.0 * subnormal * f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_mixed_infinities() {
        let q = Q32::new(
            -f32::INFINITY,
            -f32::INFINITY,
            f32::INFINITY,
            -f32::INFINITY,
        );
        let sqrt_q = q.sqrt();
        assert_eq!(sqrt_q.w, f32::INFINITY);
        assert_eq!(sqrt_q.x, -f32::INFINITY);
        assert_eq!(sqrt_q.y, f32::INFINITY);
        assert_eq!(sqrt_q.z, -f32::INFINITY);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_positive_real() {
        let q = Q64::new(4.0, 0.0, 0.0, 0.0);
        let sqrt_q = q.sqrt();
        let expected = Q64::new(2.0, 0.0, 0.0, 0.0);
        assert_eq!(sqrt_q, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_purely_imaginary() {
        let q = Q32::new(0.0, 3.0, 4.0, 0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w > 0.0);
        assert!((sqrt_q * sqrt_q - q).norm() <= 2.0 * q.norm() * f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_negative_imaginary() {
        let q = Q64::new(0.0, -3.0, -4.0, 0.0);
        let sqrt_q = q.sqrt();
        assert!(sqrt_q.w > 0.0);
        assert!((sqrt_q * sqrt_q - q).norm() <= 16.0 * q.norm() * f64::EPSILON);
    }
    #[cfg(feature = "std")]
    fn compute_hash(val: impl Hash) -> u64 {
        let mut hasher = DefaultHasher::new();
        val.hash(&mut hasher);
        hasher.finish()
    }
    #[cfg(feature = "serde")]
    #[test]
    fn test_serde_quaternion() {
        let q = Q32::new(1.0, 2.0, 3.0, 4.0);
        let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
        let deserialized: Quaternion<f32> =
            serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
        assert_eq!(q, deserialized);
    }
    #[cfg(feature = "std")]
    #[test]
    fn test_hash_of_unit_quaternion_equals_hash_of_inner_quaternion() {
        assert_eq!(
            compute_hash(UnitQuaternion::<u32>::ONE),
            compute_hash(Quaternion::<u32>::ONE)
        );
        assert_eq!(
            compute_hash(UnitQuaternion::<i32>::I),
            compute_hash(Quaternion::<i32>::I)
        );
        assert_eq!(
            compute_hash(UnitQuaternion::<isize>::J),
            compute_hash(Quaternion::<isize>::J)
        );
        assert_eq!(
            compute_hash(UnitQuaternion::<i128>::K),
            compute_hash(Quaternion::<i128>::K)
        );
    }
    #[cfg(feature = "serde")]
    #[test]
    fn test_serde_euler_angles() {
        let angles = EulerAngles {
            roll: 1.0,
            pitch: 2.0,
            yaw: 3.0,
        };
        let serialized = serde_json::to_string(&angles).expect("Failed to serialize angles");
        let deserialized: EulerAngles<f64> =
            serde_json::from_str(&serialized).expect("Failed to deserialize angles");
        assert_eq!(angles, deserialized);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_from_euler_angles() {
        assert!(
            (UQ32::from_euler_angles(core::f32::consts::PI, 0.0, 0.0).into_quaternion() - Q32::I)
                .norm()
                < f32::EPSILON
        );
        assert!(
            (UQ64::from_euler_angles(0.0, core::f64::consts::PI, 0.0).into_quaternion() - Q64::J)
                .norm()
                < f64::EPSILON
        );
        assert!(
            (UQ32::from_euler_angles(0.0, 0.0, core::f32::consts::PI).into_quaternion() - Q32::K)
                .norm()
                < f32::EPSILON
        );
        assert!(
            (UQ64::from_euler_angles(1.0, 2.0, 3.0).into_quaternion()
                - (UQ64::from_euler_angles(0.0, 0.0, 3.0)
                    * UQ64::from_euler_angles(0.0, 2.0, 0.0)
                    * UQ64::from_euler_angles(1.0, 0.0, 0.0))
                .into_quaternion())
            .norm()
                < 4.0 * f64::EPSILON
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_euler_angles() {
        let test_data = [
            Q64::new(1.0, 0.0, 0.0, 0.0),
            Q64::new(0.0, 1.0, 0.0, 0.0),
            Q64::new(0.0, 0.0, 1.0, 0.0),
            Q64::new(0.0, 0.0, 0.0, 1.0),
            Q64::new(1.0, 1.0, 1.0, 1.0),
            Q64::new(1.0, -2.0, 3.0, -4.0),
            Q64::new(4.0, 3.0, 2.0, 1.0),
        ];
        for q in test_data.into_iter().map(|q| q.normalize().unwrap()) {
            let EulerAngles { roll, pitch, yaw } = q.to_euler_angles();
            let p = UQ64::from_euler_angles(roll, pitch, yaw);
            assert!((p - q).norm() < f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_from_rotation_vector() {
        assert!(
            (UQ32::from_rotation_vector(&[core::f32::consts::PI, 0.0, 0.0]) - Q32::I).norm()
                < f32::EPSILON
        );
        assert!(
            (UQ64::from_rotation_vector(&[0.0, core::f64::consts::PI, 0.0]) - Q64::J).norm()
                < f64::EPSILON
        );
        assert!(
            (UQ32::from_rotation_vector(&[0.0, 0.0, core::f32::consts::PI]) - Q32::K).norm()
                < f32::EPSILON
        );
        let x = 2.0 * core::f64::consts::FRAC_PI_3 * (1.0f64 / 3.0).sqrt();
        assert!(
            (UQ64::from_rotation_vector(&[x, x, x]) - Q64::new(0.5, 0.5, 0.5, 0.5)).norm()
                < 4.0 * f64::EPSILON
        );
        assert!(
            (UQ64::from_rotation_vector(&[-x, x, -x]) - Q64::new(0.5, -0.5, 0.5, -0.5)).norm()
                < 4.0 * f64::EPSILON
        );
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_zero_rotation() {
        assert_eq!(UQ32::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
        assert_eq!(UQ64::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_90_degree_rotation_x_axis() {
        let q = Q32::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
        let rotation_vector = q.to_rotation_vector();
        assert!((rotation_vector[0] - core::f32::consts::FRAC_PI_2).abs() < f32::EPSILON);
        assert!((rotation_vector[1]).abs() < f32::EPSILON);
        assert!((rotation_vector[2]).abs() < f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_180_degree_rotation_y_axis() {
        let q = UQ64::J;
        let rotation_vector = q.to_rotation_vector();
        assert!((rotation_vector[0]).abs() < f64::EPSILON);
        assert!((rotation_vector[1] - core::f64::consts::PI).abs() < f64::EPSILON);
        assert!((rotation_vector[2]).abs() < f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_180_degree_rotation_arbitrary_axis() {
        let q = Q32::new(0.0, 1.0, 1.0, 1.0).normalize().unwrap();
        let rotation_vector = q.to_rotation_vector();
        let expected = [
            core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
            core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
            core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
        ];
        assert!((rotation_vector[0] - expected[0]).abs() < 4.0 * f32::EPSILON);
        assert!((rotation_vector[1] - expected[1]).abs() < 4.0 * f32::EPSILON);
        assert!((rotation_vector[2] - expected[2]).abs() < 4.0 * f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_small_rotation() {
        let angle = 1e-6f32;
        let q = Q32::new((angle / 2.0).cos(), (angle / 2.0).sin(), 0.0, 0.0)
            .normalize()
            .unwrap();
        let rotation_vector = q.to_rotation_vector();
        assert!((rotation_vector[0] - angle).abs() < f32::EPSILON);
        assert!((rotation_vector[1]).abs() < f32::EPSILON);
        assert!((rotation_vector[2]).abs() < f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_to_rotation_vector_general_case() {
        for q in [
            [1.0, 0.0, 0.0, 0.0],
            [0.0, 0.0, 0.0, 1.0],
            [1.0, 1.0, 0.0, 0.0],
            [1.0, 1.0, 1.0, 0.0],
            [1.0, 1.0, 1.0, 1.0],
            [0.0, 1.0, 1.0, -1.0],
            [1.0, 0.0, 2.0, 5.0],
            [1.0, 0.0, 1.0e-10, 2.0e-10],
            [-1.0, 0.0, 0.0, 0.0],
            [1.0, f64::EPSILON, 0.0, 0.0],
            [1.0, 0.0, 0.0, f64::MIN_POSITIVE],
            [
                -1.0,
                3.0 * f64::MIN_POSITIVE,
                2.0 * f64::MIN_POSITIVE,
                f64::MIN_POSITIVE,
            ],
        ]
        .into_iter()
        .map(|[w, x, y, z]| Q64::new(w, x, y, z).normalize().unwrap())
        {
            let rot = q.to_rotation_vector();
            let p = UQ64::from_rotation_vector(&rot);
            assert!((p - q).norm() <= 6.0 * f64::EPSILON);
        }
    }
    #[test]
    fn test_rotation_matrix_identity() {
        let q = UQ64::ONE;
        let rot_matrix = q.to_rotation_matrix3x3();
        let expected = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
        assert_eq!(rot_matrix, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_matrix_90_degrees_x() {
        let q = Q64::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
        let rot_matrix = q.to_rotation_matrix3x3();
        let expected = [
            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0,
        ];
        for (r, e) in rot_matrix.iter().zip(expected) {
            assert!((r - e).abs() <= f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_matrix_90_degrees_y() {
        let q = Q64::new(1.0, 0.0, 1.0, 0.0).normalize().unwrap();
        let rot_matrix = q.to_rotation_matrix3x3();
        let expected = [
            0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0,
        ];
        for (r, e) in rot_matrix.iter().zip(expected) {
            assert!((r - e).abs() <= f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_matrix_90_degrees_z() {
        let q = Q64::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
        let rot_matrix = q.to_rotation_matrix3x3();
        let expected = [
            0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
        ];
        for (r, e) in rot_matrix.iter().zip(expected) {
            assert!((r - e).abs() <= f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_matrix_120_degrees_xyz() {
        let q = Q64::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
        let rot_matrix = q.to_rotation_matrix3x3();
        let expected = [
            0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0,
        ];
        for (r, e) in rot_matrix.iter().zip(expected) {
            assert!((r - e).abs() <= f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_matrix_general() {
        let q = Q64::new(-1.0, 2.0, -3.0, 4.0).normalize().unwrap();
        let rot_matrix = q.to_rotation_matrix3x3();
        let [x1, y1, z1] = q.rotate_vector([1.0, 0.0, 0.0]);
        let [x2, y2, z2] = q.rotate_vector([0.0, 1.0, 0.0]);
        let [x3, y3, z3] = q.rotate_vector([0.0, 0.0, 1.0]);
        let expected = [
            x1, x2, x3, y1, y2, y3, z1, z2, z3,
        ];
        for (r, e) in rot_matrix.iter().zip(expected) {
            assert!((r - e).abs() <= f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_identity_matrix() {
        let identity: [[f32; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
        let q = UQ32::from_rotation_matrix3x3(&identity);
        let expected = UQ32::ONE;
        assert_eq!(q, expected);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_x() {
        let angle = core::f32::consts::PI / 5.0;
        let rotation_x: [[f32; 3]; 3] = [
            [1.0, 0.0, 0.0],
            [0.0, angle.cos(), angle.sin()],
            [0.0, -angle.sin(), angle.cos()],
        ];
        let q = UQ32::from_rotation_matrix3x3(&rotation_x);
        let expected = UQ32::from_rotation_vector(&[angle, 0.0, 0.0]);
        assert!((q - expected).norm() <= f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_y() {
        let angle = 4.0 * core::f32::consts::PI / 5.0;
        let rotation_y: [[f32; 3]; 3] = [
            [angle.cos(), 0.0, -angle.sin()],
            [0.0, 1.0, 0.0],
            [angle.sin(), 0.0, angle.cos()],
        ];
        let q = UQ32::from_rotation_matrix3x3(&rotation_y);
        let expected = UQ32::from_rotation_vector(&[0.0, angle, 0.0]);
        assert!((q - expected).norm() <= f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_rotation_z() {
        let angle = 3.0 * core::f64::consts::PI / 5.0;
        let rotation_z: [[f64; 3]; 3] = [
            [angle.cos(), angle.sin(), 0.0],
            [-angle.sin(), angle.cos(), 0.0],
            [0.0, 0.0, 1.0],
        ];
        let q = UQ64::from_rotation_matrix3x3(&rotation_z);
        let expected = UQ64::from_rotation_vector(&[0.0, 0.0, angle]);
        assert!((q - expected).norm() <= f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_arbitrary_rotation() {
        let arbitrary_rotation: [[f32; 3]; 3] = [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]];
        let q = UnitQuaternion::from_rotation_matrix3x3(&arbitrary_rotation);
        let expected = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
        assert!((q - expected).norm() <= f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_flat_array() {
        let angle = core::f32::consts::PI / 2.0;
        let rotation_z: [f32; 9] = [0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
        let q = UQ32::from_rotation_matrix3x3(&rotation_z);
        let expected = UQ32::from_rotation_vector(&[0.0, 0.0, angle]);
        assert!((q - expected).norm() <= f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_from_rotation_to_rotation() {
        let mat = [
            0.36f64, 0.864, -0.352, 0.48, 0.152, 0.864, 0.8, -0.48, -0.36,
        ];
        let q = UnitQuaternion::from_rotation_matrix3x3(&mat);
        let restored_mat = q.to_rotation_matrix3x3();
        for (x, e) in restored_mat.iter().zip(mat) {
            assert!((x - e).abs() <= 4.0 * f64::EPSILON);
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_zero_vector_a() {
        let a = [0.0, 0.0, 0.0];
        let b = [1.0, 0.0, 0.0];
        let q = UnitQuaternion::from_two_vectors(&a, &b);
        assert_eq!(q, UnitQuaternion::one());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_zero_vector_b() {
        let a = [1.0, 0.0, 0.0];
        let b = [0.0, 0.0, 0.0];
        let q = UnitQuaternion::from_two_vectors(&a, &b);
        assert_eq!(q, UnitQuaternion::one());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_parallel_vectors() {
        let a = [1.0, 0.0, 0.0];
        let b = [2.0, 0.0, 0.0];
        let q = UnitQuaternion::from_two_vectors(&a, &b);
        assert_eq!(q, UnitQuaternion::one());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_opposite_vectors() {
        let a = [1.0, 0.0, 0.0];
        let b = [-1.0, 0.0, 0.0];
        let q = UnitQuaternion::from_two_vectors(&a, &b);
        assert_eq!(q.as_quaternion().w, 0.0);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_perpendicular_vectors() {
        let a = [1.0f32, 0.0, 0.0];
        let b = [0.0f32, 1.0, 0.0];
        let q = UQ32::from_two_vectors(&a, &b);
        let expected = Q32::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
        assert!((q - expected).norm() <= 2.0 * f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_non_normalized_vectors() {
        let a = [0.0, 3.0, 0.0];
        let b = [0.0, 5.0, 5.0];
        let q = UQ64::from_two_vectors(&a, &b);
        let expected = Q64::new(1.0, core::f64::consts::FRAC_PI_8.tan(), 0.0, 0.0)
            .normalize()
            .unwrap();
        assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
        let a = [0.0, 3.0, 0.0];
        let b = [0.0, -5.0, 5.0];
        let q = UQ64::from_two_vectors(&a, &b);
        let expected = Q64::new(1.0, (3.0 * core::f64::consts::FRAC_PI_8).tan(), 0.0, 0.0)
            .normalize()
            .unwrap();
        assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_same_vector() {
        let a = [1.0, 1.0, 1.0];
        let q = UnitQuaternion::from_two_vectors(&a, &a);
        assert_eq!(q, UnitQuaternion::one());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_arbitrary_vectors() {
        let a = [1.0, 2.0, 3.0];
        let b = [4.0, 5.0, 6.0];
        let q = UQ64::from_two_vectors(&a, &b);
        let v = [-3.0, 6.0, -3.0]; let v_norm = 54.0f64.sqrt();
        let dir = [v[0] / v_norm, v[1] / v_norm, v[2] / v_norm];
        let cos_angle = (a[0] * b[0] + a[1] * b[1] + a[2] * b[2])
            / ((a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
                * (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]))
                .sqrt();
        let angle = cos_angle.acos();
        let expected =
            UQ64::from_rotation_vector(&[dir[0] * angle, dir[1] * angle, dir[2] * angle]);
        assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
    }
    #[test]
    fn test_default_unit_quaternion() {
        assert_eq!(UQ32::default().into_quaternion(), Q32::ONE);
    }
    #[test]
    fn test_constant_one() {
        assert_eq!(UQ32::ONE.into_quaternion(), Q32::ONE);
        assert_eq!(
            UnitQuaternion::<i32>::ONE.into_quaternion(),
            Quaternion::<i32>::ONE
        );
    }
    #[test]
    fn test_constant_i() {
        assert_eq!(UQ32::I.into_quaternion(), Q32::I);
    }
    #[test]
    fn test_constant_j() {
        assert_eq!(UQ32::J.into_quaternion(), Q32::J);
    }
    #[test]
    fn test_constant_k() {
        assert_eq!(UQ32::K.into_quaternion(), Q32::K);
    }
    #[test]
    fn test_const_one() {
        assert_eq!(<UQ32 as ConstOne>::ONE.into_quaternion(), Q32::ONE);
    }
    #[test]
    fn test_one_trait() {
        assert_eq!(<UQ32 as One>::one().into_quaternion(), Q32::ONE);
        assert!(UQ64::ONE.is_one());
        assert!(!UQ64::I.is_one());
        assert!(!UQ64::J.is_one());
        assert!(!UQ64::K.is_one());
        let mut uq = UQ32::I;
        uq.set_one();
        assert!(uq.is_one());
    }
    #[test]
    fn test_unit_quaternion_i_func() {
        assert_eq!(UQ32::i().into_quaternion(), Q32::i());
    }
    #[test]
    fn test_unit_quaternion_j_func() {
        assert_eq!(UQ32::j().into_quaternion(), Q32::j());
    }
    #[test]
    fn test_unit_quaternion_k_func() {
        assert_eq!(UQ32::k().into_quaternion(), Q32::k());
    }
    #[test]
    fn test_unit_quaternion_conj() {
        assert_eq!(UQ32::ONE.conj(), UQ32::ONE);
        assert_eq!(UQ64::I.conj(), -UQ64::I);
        assert_eq!(UQ32::J.conj(), -UQ32::J);
        assert_eq!(UQ64::K.conj(), -UQ64::K);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_conj_with_normalize() {
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj(),
            Q32::new(1.0, -2.0, -3.0, -4.0).normalize().unwrap()
        )
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_inv_func() {
        assert_eq!(
            Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().inv(),
            Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
        )
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_inv_trait() {
        assert_eq!(
            <UQ32 as Inv>::inv(Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()),
            Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
        )
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_ref_inv_trait() {
        assert_eq!(
            <&UQ32 as Inv>::inv(&Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()),
            Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
        )
    }
    #[test]
    fn test_unit_quaternion_add() {
        assert_eq!(UQ32::I + UQ32::J, Q32::new(0.0, 1.0, 1.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_add_quaternion() {
        assert_eq!(UQ32::J + Q32::K, Q32::new(0.0, 0.0, 1.0, 1.0));
    }
    #[test]
    fn test_unit_quaternion_add_underlying() {
        assert_eq!(UQ32::J + 2.0f32, Q32::new(2.0, 0.0, 1.0, 0.0));
    }
    #[test]
    fn test_f32_add_unit_quaternion() {
        assert_eq!(3.0f32 + UQ32::K, Q32::new(3.0, 0.0, 0.0, 1.0));
    }
    #[test]
    fn test_f64_add_unit_quaternion() {
        assert_eq!(4.0f64 + UQ64::I, Q64::new(4.0, 1.0, 0.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_sub() {
        assert_eq!(UQ32::I - UQ32::J, Q32::new(0.0, 1.0, -1.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_sub_quaternion() {
        assert_eq!(UQ32::J - Q32::K, Q32::new(0.0, 0.0, 1.0, -1.0));
    }
    #[test]
    fn test_unit_quaternion_sub_underlying() {
        assert_eq!(UQ32::J - 2.0f32, Q32::new(-2.0, 0.0, 1.0, 0.0));
    }
    #[test]
    fn test_f32_sub_unit_quaternion() {
        assert_eq!(3.0f32 - UQ32::K, Q32::new(3.0, 0.0, 0.0, -1.0));
    }
    #[test]
    fn test_f64_sub_unit_quaternion() {
        assert_eq!(4.0f64 - UQ64::I, Q64::new(4.0, -1.0, 0.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_mul() {
        assert_eq!(UQ32::I * UQ32::J, UQ32::K);
    }
    #[test]
    fn test_unit_quaternion_mul_quaternion() {
        assert_eq!(UQ32::J * Q32::K, Q32::new(0.0, 1.0, 0.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_mul_underlying() {
        assert_eq!(UQ32::J * 2.0f32, Q32::new(0.0, 0.0, 2.0, 0.0));
    }
    #[test]
    fn test_f32_mul_unit_quaternion() {
        assert_eq!(3.0f32 * UQ32::K, Q32::new(0.0, 0.0, 0.0, 3.0));
    }
    #[test]
    fn test_f64_mul_unit_quaternion() {
        assert_eq!(4.0f64 * UQ64::I, Q64::new(0.0, 4.0, 0.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_div() {
        assert_eq!(UQ32::I / UQ32::J, -UQ32::K);
    }
    #[test]
    fn test_unit_quaternion_div_quaternion() {
        assert_eq!(UQ32::J / Q32::K, Q32::new(0.0, -1.0, 0.0, 0.0));
    }
    #[test]
    fn test_unit_quaternion_div_underlying() {
        assert_eq!(UQ32::J / 2.0f32, Q32::new(0.0, 0.0, 0.5, 0.0));
    }
    #[test]
    fn test_f32_div_unit_quaternion() {
        assert_eq!(3.0f32 / UQ32::K, Q32::new(0.0, 0.0, 0.0, -3.0));
    }
    #[test]
    fn test_f64_div_unit_quaternion() {
        assert_eq!(4.0f64 / UQ64::I, Q64::new(0.0, -4.0, 0.0, 0.0));
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_add_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_sub_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_mul_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_div_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_add_quaternion_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_sub_quaternion_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_mul_quaternion_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_div_quaternion_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_add_real_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = 5.0;
        assert_eq!(lhs + rhs, &lhs + rhs);
        assert_eq!(lhs + rhs, lhs + &rhs);
        assert_eq!(lhs + rhs, &lhs + &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_sub_real_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = 5.0;
        assert_eq!(lhs - rhs, &lhs - rhs);
        assert_eq!(lhs - rhs, lhs - &rhs);
        assert_eq!(lhs - rhs, &lhs - &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_mul_real_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = 5.0;
        assert_eq!(lhs * rhs, &lhs * rhs);
        assert_eq!(lhs * rhs, lhs * &rhs);
        assert_eq!(lhs * rhs, &lhs * &rhs);
    }
    #[test]
    #[cfg(any(feature = "std", feature = "libm"))]
    #[allow(clippy::op_ref)]
    fn test_div_real_with_ref_unit_quaternion() {
        let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let rhs = 5.0;
        assert_eq!(lhs / rhs, &lhs / rhs);
        assert_eq!(lhs / rhs, lhs / &rhs);
        assert_eq!(lhs / rhs, &lhs / &rhs);
    }
    #[test]
    fn test_unit_quaternion_neg() {
        assert_eq!(
            (-UQ32::ONE).into_quaternion(),
            Q32::new(-1.0, 0.0, 0.0, 0.0)
        );
        assert_eq!((-UQ32::I).into_quaternion(), Q32::new(0.0, -1.0, 0.0, 0.0));
        assert_eq!((-UQ32::J).into_quaternion(), Q32::new(0.0, 0.0, -1.0, 0.0));
        assert_eq!((-UQ32::K).into_quaternion(), Q32::new(0.0, 0.0, 0.0, -1.0));
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_adjust_norm() {
        let mut q = UQ32::from_euler_angles(1.0, 0.5, 1.5);
        for _ in 0..25 {
            q = q * q;
        }
        assert!((q.into_quaternion().norm() - 1.0).abs() > 0.5);
        assert!((q.adjust_norm().into_quaternion().norm() - 1.0).abs() <= 2.0 * f32::EPSILON);
    }
    #[test]
    fn test_unit_quaternion_rotate_vector_units() {
        let v = [1.0, 2.0, 3.0];
        assert_eq!(UQ32::I.rotate_vector(v), [1.0, -2.0, -3.0]);
        assert_eq!(UQ32::J.rotate_vector(v), [-1.0, 2.0, -3.0]);
        assert_eq!(UQ32::K.rotate_vector(v), [-1.0, -2.0, 3.0]);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_unit_quaternion_rotate_vector_normalized() {
        let q = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
        let v = [1.0, 2.0, 3.0];
        let result = q.rotate_vector(v);
        assert_eq!(result, [2.0, 3.0, 1.0]);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    fn generate_unit_quaternion_data() -> impl Iterator<Item = UQ32> {
        [
            UQ32::ONE,
            UQ32::I,
            UQ32::J,
            UQ32::K,
            Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap(),
            Q32::new(10.0, 1.0, 1.0, 1.0).normalize().unwrap(),
            Q32::new(1.0, 10.0, 1.0, 1.0).normalize().unwrap(),
            Q32::new(1.0, 1.0, 3.0, 4.0).normalize().unwrap(),
            Q32::new(1.0, -1.0, 3.0, -4.0).normalize().unwrap(),
        ]
        .into_iter()
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_slerp_t_zero() {
        for q1 in generate_unit_quaternion_data() {
            for q2 in generate_unit_quaternion_data() {
                let result = q1.slerp(&q2, 0.0);
                assert!((result - q1).norm() <= f32::EPSILON);
            }
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_slerp_t_one() {
        use core::cmp::Ordering;
        for q1 in generate_unit_quaternion_data() {
            for q2 in generate_unit_quaternion_data() {
                let result = q1.slerp(&q2, 1.0);
                match q1.dot(q2).partial_cmp(&0.0) {
                    Some(Ordering::Greater) => assert!((result - q2).norm() <= f32::EPSILON),
                    Some(Ordering::Less) => assert!((result + q2).norm() <= f32::EPSILON),
                    _ => {}
                }
            }
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_slerp_t_half() {
        use core::cmp::Ordering;
        for q1 in generate_unit_quaternion_data() {
            for q2 in generate_unit_quaternion_data() {
                let result = q1.slerp(&q2, 0.5);
                let dot_sign = match q1.dot(q2).partial_cmp(&0.0) {
                    Some(Ordering::Greater) => 1.0,
                    Some(Ordering::Less) => -1.0,
                    _ => continue, };
                assert!((result - (q1 + dot_sign * q2).normalize().unwrap()).norm() <= f32::EPSILON)
            }
        }
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_slerp_small_angle() {
        let q1 = UQ32::ONE;
        let q2 = Q32::new(999_999.0, 1.0, 0.0, 0.0).normalize().unwrap();
        let t = 0.5;
        let result = q1.slerp(&q2, t);
        let expected = Q32::new(999_999.75, 0.5, 0.0, 0.0).normalize().unwrap();
        assert!((result - expected).norm() <= f32::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_of_identity() {
        assert_eq!(UQ32::ONE.sqrt(), UQ32::ONE);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_of_negative_identity() {
        let q = Q64::new(-1.0, 0.0, -0.0, -0.0).normalize().unwrap();
        assert_eq!(q.sqrt(), UQ64::I);
        assert!(q.sqrt().0.w.is_sign_positive());
        assert!(q.sqrt().0.y.is_sign_negative());
        assert!(q.sqrt().0.z.is_sign_negative());
        let q = Q64::new(-1.0, -0.0, 0.0, 0.0).normalize().unwrap();
        assert_eq!(q.sqrt(), -UQ64::I);
        assert!(q.sqrt().0.w.is_sign_positive());
        assert!(q.sqrt().0.y.is_sign_positive());
        assert!(q.sqrt().0.z.is_sign_positive());
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_general_case() {
        let c = Q64::new(1.0, 2.0, -3.0, 4.0).normalize().unwrap();
        let q = c.sqrt();
        assert!((q * q - c).norm() <= f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_with_negative_real_part() {
        let c = Q64::new(-4.0, 2.0, -3.0, 1.0).normalize().unwrap();
        let q = c.sqrt();
        assert!((q * q - c).norm() <= f64::EPSILON);
    }
    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn test_sqrt_with_subnormal_imaginary_parts() {
        let min_positive = f64::MIN_POSITIVE;
        let q = UnitQuaternion(Quaternion::new(
            -1.0,
            min_positive,
            min_positive,
            min_positive,
        ));
        let result = q.sqrt();
        let expected = UnitQuaternion(Quaternion::new(
            min_positive * 0.75f64.sqrt(),
            (1.0f64 / 3.0).sqrt(),
            (1.0f64 / 3.0).sqrt(),
            (1.0f64 / 3.0).sqrt(),
        ));
        assert!((result.0.w - expected.0.w).abs() <= 2.0 * expected.0.w * f64::EPSILON);
        assert!((result.0.x - expected.0.x).abs() <= 2.0 * expected.0.x * f64::EPSILON);
        assert!((result.0.y - expected.0.y).abs() <= 2.0 * expected.0.y * f64::EPSILON);
        assert!((result.0.z - expected.0.z).abs() <= 2.0 * expected.0.z * f64::EPSILON);
    }
    #[cfg(all(feature = "serde", any(feature = "std", feature = "libm")))]
    #[test]
    fn test_serde_unit_quaternion() {
        let q = Q64::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
        let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
        let deserialized: UQ64 =
            serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
        assert_eq!(q, deserialized);
    }
    #[cfg(feature = "serde")]
    #[test]
    fn test_serde_unit_quaternion_k() {
        let q = UQ64::K;
        let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
        let deserialized: UQ64 =
            serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
        assert_eq!(q, deserialized);
    }
}