use {
crate::UnitQuaternion,
core::ops::{Add, Mul, Neg},
num_traits::{ConstOne, ConstZero, Inv, Num, One, Zero},
};
#[cfg(any(feature = "std", feature = "libm"))]
use {
core::num::FpCategory,
num_traits::{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 one() -> Self {
Self::new(T::one(), T::zero(), T::zero(), T::zero())
}
#[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 one = T::one();
let two = one + one;
let s = T::min_positive_value();
let norm_sqr = self.norm_sqr();
if norm_sqr < T::infinity() {
if norm_sqr >= s * two {
norm_sqr.sqrt()
} else if self.is_zero() {
T::zero()
} else {
(self / s).fast_norm() * s
}
} else {
(self * s).fast_norm() / s
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float,
{
#[inline]
pub fn fast_norm(self) -> T {
self.norm_sqr().sqrt()
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float,
{
#[inline]
pub fn normalize(self) -> Option<UnitQuaternion<T>> {
UnitQuaternion::normalize(self)
}
}
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<T> From<&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.into_inner()
}
}
impl<'a, T> From<&'a UnitQuaternion<T>> for &'a Quaternion<T> {
#[inline]
fn from(q: &'a UnitQuaternion<T>) -> Self {
q.as_quaternion()
}
}
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(core::cmp::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 {
debug_assert!(
sqr_angle.is_infinite() || sqr_angle.is_nan()
);
Self::nan()
}
}
None => {
debug_assert!(result_norm.is_nan());
Self::nan()
}
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float + FloatConst,
{
#[inline]
pub fn expf(self, base: T) -> Self {
if (base.is_infinite()
&& self.w > T::zero()
&& self.x.is_zero()
&& self.y.is_zero()
&& self.z.is_zero())
|| (base.is_zero()
&& self.w < T::zero()
&& self.x.is_zero()
&& self.y.is_zero()
&& self.z.is_zero())
{
T::infinity().into()
} else {
(self * base.ln()).exp()
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float + FloatConst,
{
#[inline]
pub fn powf(self, exponent: T) -> Self {
if exponent.is_finite() {
(self.ln() * exponent).exp()
} else if exponent > T::zero() {
if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
match self.w.partial_cmp(&T::one()) {
Some(core::cmp::Ordering::Greater) => T::infinity().into(),
Some(core::cmp::Ordering::Less) => T::zero().into(),
_ => Self::nan(),
}
} else if self.norm_sqr() < T::one() {
Self::zero()
} else {
Self::nan()
}
} else if exponent < T::zero() {
if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
match self.w.partial_cmp(&T::one()) {
Some(core::cmp::Ordering::Greater) => T::zero().into(),
Some(core::cmp::Ordering::Less) => T::infinity().into(),
_ => Self::nan(),
}
} else if self.norm_sqr() > T::one() {
Self::zero()
} else {
Self::nan()
}
} else {
debug_assert!(exponent.is_nan());
Self::nan()
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float,
{
pub fn is_finite(&self) -> bool {
self.w.is_finite()
&& self.x.is_finite()
&& self.y.is_finite()
&& self.z.is_finite()
}
pub fn has_nan(&self) -> bool {
self.w.is_nan() || self.x.is_nan() || self.y.is_nan() || self.z.is_nan()
}
pub fn is_all_nan(&self) -> bool {
self.w.is_nan() && self.x.is_nan() && self.y.is_nan() && self.z.is_nan()
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float + FloatConst,
{
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 if sqr_norm_im.is_normal() {
let norm_im = sqr_norm_im.sqrt();
let f = T::PI() / norm_im + self.w.recip();
Self::new(w, f * self.x, f * self.y, f * self.z)
} else {
let f = T::one()
/ (T::min_positive_value().sqrt() * T::epsilon());
let xf = self.x * f;
let yf = self.y * f;
let zf = self.z * f;
let sqr_sum = xf * xf + yf * yf + zf * zf;
let im_norm_div_f = sqr_sum.sqrt();
let pi_div_f = T::PI() * f;
Self::new(
w,
self.x * pi_div_f / im_norm_div_f,
self.y * pi_div_f / im_norm_div_f,
self.z * pi_div_f / im_norm_div_f,
)
}
} else {
let norm_im = if sqr_norm_im.is_normal() {
sqr_norm_im.sqrt()
} else {
let f = T::one() / 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() => {
Self::new(T::neg_infinity(), self.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()
}
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<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 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))
}
}
#[cfg(test)]
mod tests {
use {
crate::{Quaternion, Q32, Q64, UQ32, UQ64},
num_traits::{ConstOne, ConstZero, Inv, One, Zero},
};
#[cfg(any(feature = "std", feature = "libm"))]
use num_traits::FloatConst;
#[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_one_func() {
assert_eq!(Q32::one(), Q32::new(1.0, 0.0, 0.0, 0.0));
}
#[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 * 0.25;
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_large_values() {
let s = f64::MAX * 0.50;
let q = Q64::new(s, s, s, s);
assert!(
(q.norm() - 2.0 * s).abs() <= 2.0 * s * f64::EPSILON,
"Norm of large values 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() {
assert_eq!(Q64::ZERO.normalize(), None);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize_infinity() {
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);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize_nan() {
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);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize_infinity_and_nan() {
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
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize_subnormal() {
let s = f64::MIN_POSITIVE / 4.0;
let q = Q64::new(s, s, s, s);
assert_eq!(
q.normalize().unwrap().into_inner(),
Q64::new(0.5, 0.5, 0.5, 0.5)
);
}
#[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_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_expf_zero_base_positive_real_quaternion() {
assert_eq!(Q64::new(1.0, 0.0, 0.0, 0.0).expf(0.0), Q64::ZERO);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_zero_base_negative_real_quaternion() {
assert_eq!(
Q32::new(-1.0, 0.0, 0.0, 0.0).expf(0.0),
f32::INFINITY.into()
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_infinity_base_positive_real_quaternion() {
let inf = f64::INFINITY;
assert_eq!(Q64::new(1.0, 0.0, 0.0, 0.0).expf(inf), inf.into());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_infinity_base_negative_real_quaternion() {
assert_eq!(
Q32::new(-1.0, 0.0, 0.0, 0.0).expf(f32::INFINITY),
0.0f32.into()
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_negative_base() {
let q = Q64::new(1.0, 0.0, 0.0, 0.0).expf(-1.0);
assert!(q.w.is_nan());
assert!(q.x.is_nan());
assert!(q.y.is_nan());
assert!(q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_nan_base() {
let q = Q32::new(1.0, 0.0, 0.0, 0.0).expf(f32::NAN);
assert!(q.w.is_nan());
assert!(q.x.is_nan());
assert!(q.y.is_nan());
assert!(q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_finite_positive_base() {
let q = Q64::new(1.0, 2.0, 3.0, 4.0);
let base = 2.0;
let result = q.expf(base);
let expected = (q * base.ln()).exp();
assert!((result - expected).norm() <= expected.norm() * f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_nan_quaternion_component() {
let q = Q64::new(f64::NAN, 1.0, 1.0, 1.0).expf(3.0);
assert!(q.w.is_nan());
assert!(q.x.is_nan());
assert!(q.y.is_nan());
assert!(q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_infinity_quaternion_component() {
let q = Q32::new(1.0, f32::INFINITY, 1.0, 1.0).expf(2.0);
assert!(q.w.is_nan());
assert!(q.x.is_nan());
assert!(q.y.is_nan());
assert!(q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_infinite_real_component_with_t_greater_than_1() {
let inf = f64::INFINITY;
assert!(!Q64::new(inf, 0.0, 0.0, 0.0).expf(5.0).is_finite());
assert!(!Q64::new(inf, 0.0, 0.0, 0.0).expf(5.0).has_nan());
assert!(!Q64::new(inf, 1.0, 2.0, 3.0).expf(42.0).is_finite());
assert!(!Q64::new(inf, 1.0, 2.0, 3.0).expf(42.0).has_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_expf_neg_infinite_real_component_with_t_between_0_and_1() {
assert!(!Q32::new(f32::NEG_INFINITY, 0.0, 0.0, 0.0)
.expf(0.5)
.is_finite());
assert!(!Q32::new(f32::NEG_INFINITY, 0.0, 0.0, 0.0)
.expf(0.5)
.has_nan());
assert!(!Q32::new(f32::NEG_INFINITY, 1.0, 2.0, 3.0)
.expf(0.75)
.is_finite());
assert!(!Q32::new(f32::NEG_INFINITY, 1.0, 2.0, 3.0)
.expf(0.75)
.has_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_zero_q_positive_t() {
let q = Quaternion::zero();
let t = 1.0;
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_negative_t() {
let q = Quaternion::new(f64::INFINITY, 0.0, 0.0, 0.0);
let t = -1.0;
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_positive_t_not_too_large() {
let q = Quaternion::new(f64::INFINITY, 0.0, 0.0, 0.0);
let t = 1.0;
let result = q.powf(t);
assert_eq!(result, f64::INFINITY.into());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_large_positive_t() {
let q = Quaternion::new(0.0, f64::INFINITY, 0.0, 0.0);
let t = f64::MAX;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_infinite_t() {
let q = Quaternion::new(f64::INFINITY, 0.0, 0.0, 0.0);
let t = f64::INFINITY;
let result = q.powf(t);
assert_eq!(result, f64::INFINITY.into());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_infinite_t_not_positive() {
let q = Quaternion::new(f64::INFINITY, 1.0, 0.0, 0.0);
let t = f64::INFINITY;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_zero_q_negative_t() {
let q = Quaternion::zero();
let t = -1.0;
let result = q.powf(t);
assert_eq!(result, f64::INFINITY.into());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_nan_q_or_t() {
let q = Quaternion::new(0.0, 0.0, f64::NAN, 0.0);
let t = 1.0;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
let q = Quaternion::one();
let t = f64::NAN;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_infinite_q_zero_t() {
let q = Quaternion::new(f64::INFINITY, 0.0, 0.0, 0.0);
let t = 0.0;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_zero_q_zero_t() {
let q = Q32::zero();
let t = 0.0;
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_non_zero_q_positive_infinite_t() {
let q = Quaternion::new(2.0, 0.0, 0.0, 0.0);
let t = f64::INFINITY;
let result = q.powf(t);
assert_eq!(result, f64::INFINITY.into());
let q = Quaternion::new(0.5, 0.0, 0.0, 0.0);
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
let q = Quaternion::new(0.25, 0.25, 0.25, 0.25);
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
let q = Quaternion::new(1.0, 0.0, 0.0, 0.0);
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_powf_non_zero_q_negative_infinite_t() {
let q = Quaternion::new(2.0, 0.0, 0.0, 0.0);
let t = f64::NEG_INFINITY;
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
let q = Quaternion::new(1.0, 0.0, 0.0, 1.0);
let result = q.powf(t);
assert_eq!(result, Quaternion::zero());
let q = Quaternion::new(0.5, 0.0, 0.0, 0.0);
let result = q.powf(t);
assert_eq!(result, f64::INFINITY.into());
let q = Quaternion::new(0.25, 0.25, 0.25, 0.25);
let result = q.powf(t);
assert!(result.is_all_nan());
let q = Quaternion::new(1.0, 0.0, 0.0, 0.0);
let result = q.powf(t);
assert!(result.w.is_nan());
assert!(result.x.is_nan());
assert!(result.y.is_nan());
assert!(result.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_is_finite_for_finite_values() {
let q = Q64::new(1.0, 2.0, 3.0, 4.0);
assert!(q.is_finite());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_is_finite_for_zero() {
let q = Q32::zero();
assert!(q.is_finite());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_is_finite_for_infinite_values() {
let inf = f32::INFINITY;
assert!(!Q32::new(inf, 1.0, 1.0, 1.0).is_finite());
assert!(!Q32::new(1.0, inf, 1.0, 1.0).is_finite());
assert!(!Q32::new(1.0, 1.0, inf, 1.0).is_finite());
assert!(!Q32::new(1.0, 1.0, 1.0, inf).is_finite());
assert!(!Q32::new(-inf, 1.0, 1.0, 1.0).is_finite());
assert!(!Q32::new(1.0, -inf, 1.0, 1.0).is_finite());
assert!(!Q32::new(1.0, 1.0, -inf, 1.0).is_finite());
assert!(!Q32::new(1.0, 1.0, 1.0, -inf).is_finite());
assert!(!Q32::new(inf, -inf, inf, -inf).is_finite());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_is_finite_for_nan_values() {
let nan = f64::NAN;
assert!(!Q64::new(nan, 1.0, 1.0, 1.0).is_finite());
assert!(!Q64::new(1.0, nan, 1.0, 1.0).is_finite());
assert!(!Q64::new(1.0, 1.0, nan, 1.0).is_finite());
assert!(!Q64::new(1.0, 1.0, 1.0, nan).is_finite());
assert!(!Q64::new(-nan, 1.0, 1.0, 1.0).is_finite());
assert!(!Q64::new(1.0, -nan, 1.0, 1.0).is_finite());
assert!(!Q64::new(1.0, 1.0, -nan, 1.0).is_finite());
assert!(!Q64::new(1.0, 1.0, 1.0, -nan).is_finite());
assert!(!Q64::new(nan, -nan, nan, -nan).is_finite());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_has_nan() {
let nan = f64::NAN;
let inf = f64::INFINITY;
assert!(!Q64::new(0.0, 0.0, 0.0, 0.0).has_nan());
assert!(!Q64::new(1.0, 1.0, 1.0, 1.0).has_nan());
assert!(!Q64::new(inf, inf, inf, inf).has_nan());
assert!(Q64::new(nan, 1.0, 1.0, 1.0).has_nan());
assert!(Q64::new(1.0, nan, 1.0, 1.0).has_nan());
assert!(Q64::new(1.0, 1.0, nan, 1.0).has_nan());
assert!(Q64::new(1.0, 1.0, 1.0, nan).has_nan());
assert!(Q64::new(-nan, 1.0, 1.0, 1.0).has_nan());
assert!(Q64::new(1.0, -nan, 1.0, 1.0).has_nan());
assert!(Q64::new(1.0, 1.0, -nan, 1.0).has_nan());
assert!(Q64::new(1.0, 1.0, 1.0, -nan).has_nan());
assert!(Q64::new(nan, -nan, nan, -nan).has_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_is_all_nan() {
let nan = f64::NAN;
let inf = f64::INFINITY;
assert!(!Q64::new(0.0, 0.0, 0.0, 0.0).is_all_nan());
assert!(!Q64::new(1.0, 1.0, 1.0, 1.0).is_all_nan());
assert!(!Q64::new(inf, inf, inf, inf).is_all_nan());
assert!(!Q64::new(nan, 1.0, 1.0, 1.0).is_all_nan());
assert!(!Q64::new(1.0, nan, 1.0, 1.0).is_all_nan());
assert!(!Q64::new(1.0, 1.0, nan, 1.0).is_all_nan());
assert!(!Q64::new(1.0, 1.0, 1.0, nan).is_all_nan());
assert!(!Q64::new(-nan, 1.0, 1.0, 1.0).is_all_nan());
assert!(!Q64::new(1.0, -nan, 1.0, 1.0).is_all_nan());
assert!(!Q64::new(1.0, 1.0, -nan, 1.0).is_all_nan());
assert!(!Q64::new(1.0, 1.0, 1.0, -nan).is_all_nan());
assert!(Q64::new(nan, nan, nan, nan).is_all_nan());
assert!(Q64::new(-nan, -nan, -nan, -nan).is_all_nan());
assert!(Q64::new(nan, -nan, nan, -nan).is_all_nan());
}
#[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, 0.0, 0.0, 0.0);
assert_eq!(ln_q.w, expected.w);
assert_eq!(ln_q.x, expected.x);
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_ln_negative_real_part_tiny_imaginary_part() {
use core::f32;
let q = Q32::new(-2.0, 346.0 * f32::EPSILON, 0.0, 0.0);
let ln_q = q.ln();
let expected =
Q32::new(2.0f32.ln(), f32::consts::PI + q.x / q.w, 0.0, 0.0);
assert!((ln_q - expected).norm() <= 8.0 * f32::EPSILON);
let q = Q32::new(-3.0, f32::MIN_POSITIVE / 64.0, 0.0, 0.0);
let ln_q = q.ln();
let expected = Q32::new(3.0f32.ln(), f32::consts::PI, 0.0, 0.0);
assert_eq!(ln_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_tiny_real_part_and_tiny_imaginary_part() {
use core::f32;
let w = f32::MIN_POSITIVE.sqrt();
let q = Q32::new(w, 0.0, w / 2.0, 0.0);
let ln_q = q.ln();
let expected = Q32::new(
(1.25 * f32::MIN_POSITIVE).ln() / 2.0,
0.0,
0.5f32.atan(),
0.0,
);
assert_eq!(ln_q, expected);
let w = f32::MIN_POSITIVE;
let q = Q32::new(w, w, w, w);
let ln_q = q.ln();
let expected = Q32::new(
(2.0 * f32::MIN_POSITIVE).ln(),
f32::consts::PI / 27.0f32.sqrt(),
f32::consts::PI / 27.0f32.sqrt(),
f32::consts::PI / 27.0f32.sqrt(),
);
assert!(
(ln_q - expected).norm() <= expected.norm() * 2.0 * f32::EPSILON
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_very_large_inputs() {
use core::f64;
let q = Q64::new(f64::MAX, 0.0, 0.0, f64::MAX);
let ln_q = q.ln();
let expected = Q64::new(
f64::MAX.ln() + 2.0f64.ln() / 2.0,
0.0,
0.0,
f64::consts::PI / 4.0,
);
assert!(
(ln_q - expected).norm() <= expected.norm() * 2.0 * f64::EPSILON
);
}
#[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(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_negative_real_part_subnormal_imaginary_part() {
let q = Q32::new(-1.0, f32::MIN_POSITIVE / 64.0, 0.0, 0.0);
let sqrt_q = q.sqrt();
let expected = Q32::new(q.x / 2.0, 1.0, 0.0, 0.0);
assert_eq!(sqrt_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_for_overflowing_norm_sqr_of_input() {
let n = f64::MAX / 2.0;
let q = Q64::new(-n, n, n, n);
let sqrt_q = q.sqrt();
let sqrt_n = f64::MAX.sqrt() / 2.0;
let expected = Q64::new(sqrt_n, sqrt_n, sqrt_n, sqrt_n);
assert!((sqrt_q - expected).norm() <= expected.norm() * f64::EPSILON);
}
#[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);
}
}