num_quaternion/
pure_quaternion.rs

1#![cfg(feature = "unstable")]
2
3use core::ops::{Add, Mul, Neg};
4
5use num_traits::{ConstOne, ConstZero, Inv, Num, One, Zero};
6
7#[cfg(any(feature = "std", feature = "libm"))]
8use num_traits::{Float, FloatConst};
9
10#[cfg(any(feature = "std", feature = "libm"))]
11use crate::UnitQuaternion;
12
13/// A pure quaternion, i.e. a quaternion with a real part of zero.
14///
15/// A pure quaternion is a quaternion of the form $bi + cj + dk$.
16/// Computations with pure quaternions can be more efficient than with general
17/// quaternions. Apart from that, pure quaternions are used to represent
18/// 3D vectors and provide the compile-time guarantee that the real part is
19/// zero.
20///
21/// The `PureQuaternion` struct is kept as similar as possible to the
22/// [`Quaternion`] struct with respect to its API. It provides
23///
24///   - a constructor [`PureQuaternion::new`] to create a new pure quaternion,
25///   - member data fields `x`, `y`, and `z` to access the coefficients of $i$,
26///     $j$, and $k$, respectively,
27///   - The types aliases [`PQ32`] and [`PQ64`] are provided for
28///     `PureQuaternion<f32>` and `PureQuaternion<f64>`, respectively.
29#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
30pub struct PureQuaternion<T> {
31    /// The coefficient of $i$.
32    pub x: T,
33    /// The coefficient of $j$.
34    pub y: T,
35    /// The coefficient of $k$.
36    pub z: T,
37}
38
39/// Alias for a [`PureQuaternion<f32>`].
40pub type PQ32 = PureQuaternion<f32>;
41/// Alias for a [`PureQuaternion<f64>`].
42pub type PQ64 = PureQuaternion<f64>;
43
44impl<T> PureQuaternion<T> {
45    /// Constructs a new pure quaternion.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// # use num_quaternion::PureQuaternion;
51    /// let q = PureQuaternion::new(1.0, 2.0, 3.0);
52    /// ```
53    #[inline]
54    pub const fn new(x: T, y: T, z: T) -> Self {
55        Self { x, y, z }
56    }
57}
58
59impl<T> PureQuaternion<T>
60where
61    T: ConstZero,
62{
63    /// Constructs a new pure quaternion with all components set to zero.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// # use num_quaternion::PureQuaternion;
69    /// let q = PureQuaternion::ZERO;
70    /// assert_eq!(q, PureQuaternion::new(0.0, 0.0, 0.0));
71    /// ```
72    pub const ZERO: Self = Self::new(T::ZERO, T::ZERO, T::ZERO);
73}
74
75impl<T> ConstZero for PureQuaternion<T>
76where
77    T: ConstZero,
78{
79    const ZERO: Self = Self::ZERO;
80}
81
82impl<T> Zero for PureQuaternion<T>
83where
84    T: Zero,
85{
86    #[inline]
87    fn zero() -> Self {
88        Self::new(T::zero(), T::zero(), T::zero())
89    }
90
91    #[inline]
92    fn is_zero(&self) -> bool {
93        self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
94    }
95
96    #[inline]
97    fn set_zero(&mut self) {
98        self.x.set_zero();
99        self.y.set_zero();
100        self.z.set_zero();
101    }
102}
103
104impl<T> PureQuaternion<T>
105where
106    T: ConstZero + ConstOne,
107{
108    /// A constant `PureQuaternion` of value $i$.
109    ///
110    /// See also [`Quaternion::I`](crate::Quaternion::I), [`PureQuaternion::i`].
111    ///
112    /// # Example
113    ///
114    /// ```
115    /// # use num_quaternion::PureQuaternion;
116    /// let q = PureQuaternion::I;
117    /// assert_eq!(q, PureQuaternion::new(1.0, 0.0, 0.0));
118    /// ```
119    pub const I: Self = Self::new(T::ONE, T::ZERO, T::ZERO);
120
121    /// A constant `PureQuaternion` of value $j$.
122    ///
123    /// See also [`Quaternion::J`](crate::Quaternion::J), [`PureQuaternion::j`].
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// # use num_quaternion::PureQuaternion;
129    /// let q = PureQuaternion::J;
130    /// assert_eq!(q, PureQuaternion::new(0.0, 1.0, 0.0));
131    /// ```
132    pub const J: Self = Self::new(T::ZERO, T::ONE, T::ZERO);
133
134    /// A constant `PureQuaternion` of value $k$.
135    ///
136    /// See also [`Quaternion::K`](crate::Quaternion::K), [`PureQuaternion::k`].
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// # use num_quaternion::PureQuaternion;
142    /// let q = PureQuaternion::K;
143    /// assert_eq!(q, PureQuaternion::new(0.0, 0.0, 1.0));
144    /// ```
145    pub const K: Self = Self::new(T::ZERO, T::ZERO, T::ONE);
146}
147
148impl<T> PureQuaternion<T>
149where
150    T: Zero + One,
151{
152    /// Returns the imaginary unit $i$.
153    ///
154    /// See also [`Quaternion::i`](crate::Quaternion::i), [`PureQuaternion::I`].
155    ///
156    /// # Example
157    ///
158    /// ```
159    /// # use num_quaternion::PureQuaternion;
160    /// let q = PureQuaternion::i();
161    /// assert_eq!(q, PureQuaternion::new(1.0, 0.0, 0.0));
162    /// ```
163    #[inline]
164    pub fn i() -> Self {
165        Self::new(T::one(), T::zero(), T::zero())
166    }
167
168    /// Returns the imaginary unit $j$.
169    ///
170    /// See also [`Quaternion::j`](crate::Quaternion::j), [`PureQuaternion::J`].
171    ///
172    /// # Example
173    ///
174    /// ```
175    /// # use num_quaternion::PureQuaternion;
176    /// let q = PureQuaternion::j();
177    /// assert_eq!(q, PureQuaternion::new(0.0, 1.0, 0.0));
178    /// ```
179    #[inline]
180    pub fn j() -> Self {
181        Self::new(T::zero(), T::one(), T::zero())
182    }
183
184    /// Returns the imaginary unit $k$.
185    ///
186    /// See also [`Quaternion::k`](crate::Quaternion::k), [`PureQuaternion::K`].
187    ///
188    /// # Example
189    ///
190    /// ```
191    /// # use num_quaternion::PureQuaternion;
192    /// let q = PureQuaternion::k();
193    /// assert_eq!(q, PureQuaternion::new(0.0, 0.0, 1.0));
194    /// ```
195    #[inline]
196    pub fn k() -> Self {
197        Self::new(T::zero(), T::zero(), T::one())
198    }
199}
200
201#[cfg(any(feature = "std", feature = "libm"))]
202impl<T> PureQuaternion<T>
203where
204    T: Float,
205{
206    /// Returns a pure quaternion filled with `NaN` values.
207    ///
208    /// # Example
209    ///
210    /// ```
211    /// # use num_quaternion::PQ32;
212    /// let q = PQ32::nan();
213    /// assert!(q.x.is_nan());
214    /// assert!(q.y.is_nan());
215    /// assert!(q.z.is_nan());
216    /// ```
217    #[inline]
218    pub fn nan() -> Self {
219        let nan = T::nan();
220        Self::new(nan, nan, nan)
221    }
222}
223
224impl<T> PureQuaternion<T>
225where
226    T: Clone + Mul<T, Output = T> + Add<T, Output = T>,
227{
228    /// Returns the square of the norm.
229    ///
230    /// The result is $x^2 + y^2 + z^2$ with some rounding errors.
231    /// The rounding error is at most 2
232    /// [ulps](https://en.wikipedia.org/wiki/Unit_in_the_last_place).
233    ///
234    /// This is guaranteed to be more efficient than [`norm`](Quaternion::norm()).
235    /// Furthermore, `T` only needs to support addition and multiplication
236    /// and therefore, this function works for more types than
237    /// [`norm`](Quaternion::norm()).
238    ///
239    /// # Example
240    ///
241    /// ```
242    /// # use num_quaternion::PureQuaternion;
243    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
244    /// assert_eq!(q.norm_sqr(), 14.0);
245    /// ```
246    #[inline]
247    pub fn norm_sqr(&self) -> T {
248        self.x.clone() * self.x.clone()
249            + self.y.clone() * self.y.clone()
250            + self.z.clone() * self.z.clone()
251    }
252}
253
254impl<T> PureQuaternion<T>
255where
256    T: Clone + Neg<Output = T>,
257{
258    /// Returns the conjugate of the pure quaternion, i. e. its negation.
259    ///
260    /// # Example
261    ///
262    /// ```
263    /// # use num_quaternion::PureQuaternion;
264    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
265    /// assert_eq!(q.conj(), PureQuaternion::new(-1.0, -2.0, -3.0));
266    /// ```
267    #[inline]
268    pub fn conj(&self) -> Self {
269        Self::new(-self.x.clone(), -self.y.clone(), -self.z.clone())
270    }
271}
272
273impl<T> PureQuaternion<T>
274where
275    for<'a> &'a Self: Inv<Output = PureQuaternion<T>>,
276{
277    /// Returns the inverse of the pure quaternion.
278    ///
279    /// # Example
280    ///
281    /// ```
282    /// # use num_quaternion::PureQuaternion;
283    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
284    /// assert_eq!(q.inv(), PureQuaternion::new(
285    ///     -1.0 / 14.0, -2.0 / 14.0, -3.0 / 14.0));
286    /// ```
287    #[inline]
288    pub fn inv(&self) -> Self {
289        Inv::inv(self)
290    }
291}
292
293impl<T> Inv for &PureQuaternion<T>
294where
295    T: Clone + Neg<Output = T> + Num,
296{
297    type Output = PureQuaternion<T>;
298
299    #[inline]
300    fn inv(self) -> Self::Output {
301        let norm_sqr = self.norm_sqr();
302        PureQuaternion::new(
303            -self.x.clone() / norm_sqr.clone(),
304            -self.y.clone() / norm_sqr.clone(),
305            -self.z.clone() / norm_sqr,
306        )
307    }
308}
309
310impl<T> Inv for PureQuaternion<T>
311where
312    for<'a> &'a Self: Inv<Output = PureQuaternion<T>>,
313{
314    type Output = PureQuaternion<T>;
315
316    #[inline]
317    fn inv(self) -> Self::Output {
318        Inv::inv(&self)
319    }
320}
321
322#[cfg(any(feature = "std", feature = "libm"))]
323impl<T> PureQuaternion<T>
324where
325    T: Float,
326{
327    /// Calculates |self|.
328    ///
329    /// The result is $\sqrt{x^2+y^2+z^2}$ with some possible rounding
330    /// errors. The total relative rounding error is at most two
331    /// [ulps](https://en.wikipedia.org/wiki/Unit_in_the_last_place).
332    ///
333    /// If any of the components of the input quaternion is `NaN`, then `NaN`
334    /// is returned. Otherwise, if any of the components is infinite, then
335    /// a positive infinite value is returned.
336    ///
337    /// # Example
338    ///
339    /// ```
340    /// # use num_quaternion::PureQuaternion;
341    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
342    /// assert_eq!(q.norm(), 14.0f32.sqrt());
343    /// ```
344    #[inline]
345    pub fn norm(self) -> T {
346        let one = T::one();
347        let two = one + one;
348        let s = T::min_positive_value();
349        let norm_sqr = self.norm_sqr();
350        if norm_sqr < T::infinity() {
351            if norm_sqr >= s * two {
352                norm_sqr.sqrt()
353            } else if self.is_zero() {
354                // Likely, the whole vector is zero. If so, we can return
355                // zero directly and avoid expensive floating point math.
356                T::zero()
357            } else {
358                // Otherwise, scale up, such that the norm will be in the
359                // normal floating point range, then scale down the result.
360                (self / s).fast_norm() * s
361            }
362        } else {
363            // There are three possible cases:
364            //   1. one of x, y, z is NaN,
365            //   2. neither is `NaN`, but at least one of them is infinite, or
366            //   3. all of them are finite.
367            // In the first case, multiplying by s or dividing by it does not
368            // change the that the result is `NaN`. The same applies in the
369            // second case: the result remains infinite. In the third case,
370            // multiplying by s makes sure that the square norm is a normal
371            // floating point number. Dividing by it will rescale the result
372            // to the correct magnitude.
373            (self * s).fast_norm() / s
374        }
375    }
376}
377
378#[cfg(any(feature = "std", feature = "libm"))]
379impl<T> PureQuaternion<T>
380where
381    T: Float,
382{
383    /// Calculates |self| without branching.
384    ///
385    /// This function returns the same result as [`norm`](Self::norm), if
386    /// |self|² is a normal floating point number (i. e. there is no overflow
387    /// nor underflow), or if `self` is zero. In these cases the maximum
388    /// relative error of the result is guaranteed to be less than two ulps.
389    /// In all other cases, there's no guarantee on the precision of the
390    /// result:
391    ///
392    /// * If |self|² overflows, then $\infty$ is returned.
393    /// * If |self|² underflows to zero, then zero will be returned.
394    /// * If |self|² is a subnormal number (very small floating point value
395    ///   with reduced relative precision), then the result is the square
396    ///   root of that.
397    ///
398    /// In other words, this function can be imprecise for very large and very
399    /// small floating point numbers, but it is generally faster than
400    /// [`norm`](Self::norm), because it does not do any branching. So if you
401    /// are interested in maximum speed of your code, feel free to use this
402    /// function. If you need to be precise results for the whole range of the
403    /// floating point type `T`, stay with [`norm`](Self::norm).
404    ///
405    /// # Example
406    ///
407    /// ```
408    /// # use num_quaternion::PureQuaternion;
409    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
410    /// assert_eq!(q.fast_norm(), q.norm());
411    /// ```
412    #[inline]
413    pub fn fast_norm(self) -> T {
414        self.norm_sqr().sqrt()
415    }
416}
417
418#[cfg(any(feature = "std", feature = "libm"))]
419impl<T> PureQuaternion<T>
420where
421    T: Float + FloatConst,
422{
423    /// Given a pure quaternion $q$, returns $e^q$, where $e$ is the base of
424    /// the natural logarithm.
425    ///
426    /// This method computes the exponential of a quaternion, handling various
427    /// edge cases to ensure numerical stability and correctness:
428    ///
429    /// 1. **NaN Input**: If any component of the input quaternion is `NaN`,
430    ///    the method returns a quaternion filled with `NaN` values.
431    ///
432    /// 2. **Large Norm**: If the norm of the pure quaternion is too large,
433    ///    the method may return a `NaN` quaternion or a quaternion with the
434    ///    correct magnitude but inaccurate direction.
435    ///
436    /// # Example
437    ///
438    /// ```
439    /// # use num_quaternion::PureQuaternion;
440    /// let q = PureQuaternion::new(1.0f32, 2.0, 3.0);
441    /// let exp_q = q.exp();
442    /// ```
443    pub fn exp(self) -> UnitQuaternion<T> {
444        let one = T::one();
445
446        // Compute the squared norm of the imaginary part
447        let sqr_angle = self.x * self.x + self.y * self.y + self.z * self.z;
448
449        if sqr_angle <= T::epsilon() {
450            // Use Taylor series approximation for small angles to
451            // maintain numerical stability. By Taylor expansion of
452            // `cos(angle)` we get
453            //     cos(angle) >= 1 - angle² / 2
454            // and thus |cos(angle) - 1| is less than half a floating
455            // point epsilon. Similarly,
456            //     sinc(angle) >= 1 - angle² / 6
457            // and thus |sinc(angle) - 1| is less than a sixth of a
458            // floating point epsilon.
459            UnitQuaternion::new(one, self.x, self.y, self.z)
460        } else {
461            // Standard computation for larger angles
462            let angle = sqr_angle.sqrt();
463            let cos_angle = angle.cos();
464            let sinc_angle = angle.sin() / angle;
465            let w = cos_angle;
466            let x = self.x * sinc_angle;
467            let y = self.y * sinc_angle;
468            let z = self.z * sinc_angle;
469            UnitQuaternion::new(w, x, y, z)
470        }
471    }
472}
473
474#[cfg(feature = "serde")]
475impl<T> serde::Serialize for PureQuaternion<T>
476where
477    T: serde::Serialize,
478{
479    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
480    where
481        S: serde::Serializer,
482    {
483        (&self.x, &self.y, &self.z).serialize(serializer)
484    }
485}
486
487#[cfg(feature = "serde")]
488impl<'de, T> serde::Deserialize<'de> for PureQuaternion<T>
489where
490    T: serde::Deserialize<'de>,
491{
492    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
493    where
494        D: serde::Deserializer<'de>,
495    {
496        let (x, y, z) = serde::Deserialize::deserialize(deserializer)?;
497        Ok(PureQuaternion::new(x, y, z))
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    #[cfg(any(feature = "std", feature = "libm"))]
504    use core::f32;
505
506    use super::*;
507
508    #[test]
509    fn test_new() {
510        let q = PQ32::new(1.0, 2.0, 3.0);
511        assert_eq!(q.x, 1.0);
512        assert_eq!(q.y, 2.0);
513        assert_eq!(q.z, 3.0);
514    }
515
516    #[test]
517    fn test_zero_const() {
518        let q = PQ64::ZERO;
519        assert_eq!(q.x, 0.0);
520        assert_eq!(q.y, 0.0);
521        assert_eq!(q.z, 0.0);
522    }
523
524    #[test]
525    fn test_const_zero() {
526        let q: PQ32 = ConstZero::ZERO;
527        assert_eq!(q.x, 0.0);
528        assert_eq!(q.y, 0.0);
529        assert_eq!(q.z, 0.0);
530    }
531
532    #[test]
533    fn test_zero_trait() {
534        let q = PQ32::zero();
535        assert_eq!(q.x, 0.0);
536        assert_eq!(q.y, 0.0);
537        assert_eq!(q.z, 0.0);
538        assert!(q.is_zero());
539
540        let mut q = PQ32::new(1.0, 2.0, 3.0);
541        assert!(!q.is_zero());
542        q.set_zero();
543        assert!(q.is_zero());
544    }
545
546    #[test]
547    fn test_i_const() {
548        let q = PQ32::I;
549        assert_eq!(q.x, 1.0);
550        assert_eq!(q.y, 0.0);
551        assert_eq!(q.z, 0.0);
552    }
553
554    #[test]
555    fn test_j_const() {
556        let q = PQ64::J;
557        assert_eq!(q.x, 0.0);
558        assert_eq!(q.y, 1.0);
559        assert_eq!(q.z, 0.0);
560    }
561
562    #[test]
563    fn test_k_const() {
564        let q = PQ32::K;
565        assert_eq!(q.x, 0.0);
566        assert_eq!(q.y, 0.0);
567        assert_eq!(q.z, 1.0);
568    }
569
570    #[test]
571    fn test_i_static() {
572        let q = PQ64::i();
573        assert_eq!(q.x, 1.0);
574        assert_eq!(q.y, 0.0);
575        assert_eq!(q.z, 0.0);
576    }
577
578    #[test]
579    fn test_j_static() {
580        let q = PQ32::j();
581        assert_eq!(q.x, 0.0);
582        assert_eq!(q.y, 1.0);
583        assert_eq!(q.z, 0.0);
584    }
585
586    #[test]
587    fn test_k_static() {
588        let q = PQ64::k();
589        assert_eq!(q.x, 0.0);
590        assert_eq!(q.y, 0.0);
591        assert_eq!(q.z, 1.0);
592    }
593
594    #[cfg(any(feature = "std", feature = "libm"))]
595    #[test]
596    fn test_nan() {
597        let q = PQ32::nan();
598        assert!(q.x.is_nan());
599        assert!(q.y.is_nan());
600        assert!(q.z.is_nan());
601    }
602
603    #[test]
604    fn test_norm_sqr() {
605        let q = PQ64::new(1.0, 2.0, 3.0);
606        assert_eq!(q.norm_sqr(), 14.0);
607    }
608
609    #[test]
610    fn test_conj() {
611        let q = PQ32::new(1.0, 2.0, 3.0);
612        assert_eq!(q.conj(), PureQuaternion::new(-1.0, -2.0, -3.0));
613    }
614
615    #[test]
616    fn test_inv() {
617        let q = PQ64::new(1.0, 2.0, 3.0);
618        assert_eq!(
619            q.inv(),
620            PureQuaternion::new(-1.0 / 14.0, -2.0 / 14.0, -3.0 / 14.0)
621        );
622    }
623
624    #[test]
625    fn test_inv_trait() {
626        let q = PQ32::new(1.0, 2.0, 3.0);
627        assert_eq!(
628            Inv::inv(&q),
629            PureQuaternion::new(-1.0 / 14.0, -2.0 / 14.0, -3.0 / 14.0)
630        );
631    }
632
633    #[cfg(any(feature = "std", feature = "libm"))]
634    #[test]
635    fn test_norm_normal_values() {
636        let q = PQ64::new(1.0, 2.0, 3.0);
637        assert_eq!(q.norm(), 14.0f64.sqrt());
638    }
639
640    #[cfg(any(feature = "std", feature = "libm"))]
641    #[test]
642    fn test_norm_zero_quaternion() {
643        let q = PQ32::zero();
644        assert_eq!(q.norm(), 0.0);
645    }
646
647    #[cfg(any(feature = "std", feature = "libm"))]
648    #[test]
649    fn test_norm_subnormal_values() {
650        let s = f64::MIN_POSITIVE * 0.25;
651        let q = PQ64::new(s, s, s);
652        assert!(
653            (q.norm() - s * 3.0f64.sqrt()).abs() < 4.0 * s * f64::EPSILON,
654            "Norm of subnormal quaternion is not accurate."
655        )
656    }
657
658    #[cfg(any(feature = "std", feature = "libm"))]
659    #[test]
660    fn test_norm_large_values() {
661        let s = f64::MAX * 0.5;
662        let q = PQ64::new(s, s, s);
663        assert!(
664            (q.norm() - s * 3.0f64.sqrt()).abs() < 2.0 * s * f64::EPSILON,
665            "Norm of large quaternion is not accurate."
666        );
667    }
668
669    #[cfg(any(feature = "std", feature = "libm"))]
670    #[test]
671    fn test_norm_infinite_values() {
672        let inf = f32::INFINITY;
673        assert_eq!(PQ32::new(inf, 1.0, 1.0).norm(), inf);
674        assert_eq!(PQ32::new(1.0, inf, 1.0).norm(), inf);
675        assert_eq!(PQ32::new(1.0, 1.0, inf).norm(), inf);
676    }
677
678    #[cfg(any(feature = "std", feature = "libm"))]
679    #[test]
680    fn test_norm_nan_values() {
681        let nan = f32::NAN;
682        assert!(PQ32::new(nan, 1.0, 1.0).norm().is_nan());
683        assert!(PQ32::new(1.0, nan, 1.0).norm().is_nan());
684        assert!(PQ32::new(1.0, 1.0, nan).norm().is_nan());
685    }
686
687    #[cfg(any(feature = "std", feature = "libm"))]
688    #[test]
689    fn test_fast_norm_normal_values() {
690        let q = PQ64::new(1.1, 2.7, 3.4);
691        assert_eq!(q.fast_norm(), q.norm());
692    }
693
694    #[cfg(any(feature = "std", feature = "libm"))]
695    #[test]
696    fn test_fast_norm_zero_quaternion() {
697        let q = PQ32::zero();
698        assert_eq!(q.fast_norm(), 0.0);
699    }
700
701    #[cfg(any(feature = "std", feature = "libm"))]
702    #[test]
703    fn test_fast_norm_infinite_values() {
704        let inf = f32::INFINITY;
705        assert_eq!(PQ32::new(inf, 1.0, 1.0).fast_norm(), inf);
706        assert_eq!(PQ32::new(1.0, inf, 1.0).fast_norm(), inf);
707        assert_eq!(PQ32::new(1.0, 1.0, inf).fast_norm(), inf);
708    }
709
710    #[cfg(any(feature = "std", feature = "libm"))]
711    #[test]
712    fn test_fast_norm_nan_values() {
713        let nan = f32::NAN;
714        assert!(PQ32::new(nan, 1.0, 1.0).fast_norm().is_nan());
715        assert!(PQ32::new(1.0, nan, 1.0).fast_norm().is_nan());
716        assert!(PQ32::new(1.0, 1.0, nan).fast_norm().is_nan());
717    }
718
719    #[cfg(any(feature = "std", feature = "libm"))]
720    #[test]
721    fn test_exp_zero_quaternion() {
722        assert_eq!(PQ64::ZERO.exp(), UnitQuaternion::ONE);
723    }
724
725    #[cfg(any(feature = "std", feature = "libm"))]
726    #[test]
727    fn test_exp_i_quaternion() {
728        let q = PQ32::I;
729        let exp_q = q.exp();
730        let expected =
731            UnitQuaternion::new(1.0f32.cos(), 1.0f32.sin(), 0.0, 0.0);
732        assert_eq!(exp_q, expected);
733    }
734
735    #[cfg(any(feature = "std", feature = "libm"))]
736    #[test]
737    fn test_exp_complex_quaternion() {
738        let q = PQ64::new(1.0, 1.0, 1.0);
739        let exp_q = q.exp();
740        let angle = 3.0f64.sqrt();
741        let re = angle.cos();
742        let im = angle.sin() / angle;
743        let expected = UnitQuaternion::new(re, im, im, im);
744        assert!((exp_q - expected).norm() <= 2.0 * f64::EPSILON);
745    }
746
747    #[cfg(any(feature = "std", feature = "libm"))]
748    #[test]
749    fn test_exp_nan_quaternion() {
750        for q in [
751            PQ32::new(f32::NAN, 1.0, 1.0),
752            PQ32::new(1.0, f32::NAN, 1.0),
753            PQ32::new(1.0, 1.0, f32::NAN),
754        ]
755        .iter()
756        {
757            let exp_q = q.exp();
758            assert!(exp_q.as_quaternion().w.is_nan());
759            assert!(exp_q.as_quaternion().x.is_nan());
760            assert!(exp_q.as_quaternion().y.is_nan());
761            assert!(exp_q.as_quaternion().z.is_nan());
762        }
763    }
764
765    #[cfg(any(feature = "std", feature = "libm"))]
766    #[test]
767    fn test_exp_large_imaginary_norm() {
768        let q = PQ32::new(1e30, 1e30, 1e30);
769        let exp_q = q.exp();
770        assert!(exp_q.as_quaternion().w.is_nan());
771        assert!(exp_q.as_quaternion().x.is_nan());
772        assert!(exp_q.as_quaternion().y.is_nan());
773        assert!(exp_q.as_quaternion().z.is_nan());
774    }
775
776    #[cfg(any(feature = "std", feature = "libm"))]
777    #[test]
778    fn test_exp_infinite_imaginary_part() {
779        let q = PQ64::new(1.0, 1.0, f64::INFINITY);
780        let exp_q = q.exp();
781        assert!(exp_q.as_quaternion().w.is_nan());
782        assert!(exp_q.as_quaternion().x.is_nan());
783        assert!(exp_q.as_quaternion().y.is_nan());
784        assert!(exp_q.as_quaternion().z.is_nan());
785    }
786
787    #[cfg(any(feature = "std", feature = "libm"))]
788    #[test]
789    fn test_exp_small_imaginary_norm() {
790        let epsilon = f32::EPSILON;
791        let q = PQ32::new(epsilon, epsilon, epsilon);
792        let exp_q = q.exp();
793        let expected = UnitQuaternion::new(1.0, epsilon, epsilon, epsilon);
794        assert!((exp_q - expected).norm() <= 0.5 * f32::EPSILON);
795    }
796
797    #[cfg(feature = "serde")]
798    #[test]
799    fn test_serde_pure_quaternion() {
800        let q = PQ32::new(1.0, 2.0, 3.0);
801        let serialized =
802            serde_json::to_string(&q).expect("Failed to serialize quaternion");
803
804        let deserialized: PQ32 = serde_json::from_str(&serialized)
805            .expect("Failed to deserialize quaternion");
806        assert_eq!(deserialized, q);
807    }
808}