Repository: bitshifter/glam-rs
Pull request #425 (2023-08-30 20:50:57)

Implement `MulAssign` for affine types


Patch:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 6ed2dcb..7d67650 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -13,7 +13,7 @@ jobs:
       #   run: cargo install cargo-tarpaulin
 
       - name: Generate code coverage
-        run: cargo +nightly tarpaulin --timeout 120 --out Lcov --output-dir ./coverage
+        run: cargo +nightly tarpaulin --timeout 120 --out lcov --output-dir ./coverage
 
       - name: Upload to coveralls.io
         uses: coverallsapp/github-action@master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e18260..f43a8a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog], and this project adheres to
 [Semantic Versioning].
 
+## [Unreleased]
+
+### Fixed
+
+* Fixed singularities in `Quat::to_euler`.
+
+### Added
+
+* Added `div_euclid` and `rem_euclid` to integer vector types.
+
+* Added wrapping and saturating arithmetic operations to integer vector types.
+
+* Added `to_scale_angle_translation` to 2D affine types.
+
+### Changed
+
+* Disable default features on optional `rkyv` dependency.
+
 ## [0.24.1] - 2023-06-24
 
 ### Added
@@ -14,7 +32,7 @@ The format is based on [Keep a Changelog], and this project adheres to
 
 * Added missing safe `From` conversions from `f32` vectors to `f64` vectors.
 
-* Added `TryFrom` implemetations between different vector types.
+* Added `TryFrom` implementations between different vector types.
 
 * Added `test` and `set` methods to `bool` vector types for testing and setting
   individual mask elements.
diff --git a/codegen/templates/affine.rs.tera b/codegen/templates/affine.rs.tera
index 40c3e16..09e889d 100644
--- a/codegen/templates/affine.rs.tera
+++ b/codegen/templates/affine.rs.tera
@@ -47,7 +47,7 @@ use crate::{
     {{ mat_t }}, {{ col_t}}, {{ mat4_t }}, {{ quat_t }},
 {% endif %}
 };
-use core::ops::{Deref, DerefMut, Mul};
+use core::ops::{Deref, DerefMut, Mul, MulAssign};
 
 /// A {{ dim }}D affine transform, which can represent translation, rotation, scaling and shear.
 {%- if is_align %}
@@ -286,6 +286,33 @@ impl {{ self_t }} {
     }
 {% endif %}
 
+    /// Extracts `scale`, `angle` and `translation` from `self`.
+    ///
+    /// The transform is expected to be non-degenerate and without shearing, or the output
+    /// will be invalid.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the determinant `self.matrix2` is zero or if the resulting scale
+    /// vector contains any zero elements when `glam_assert` is enabled.
+    #[inline]
+    pub fn to_scale_angle_translation(self) -> ({{ vec2_t }}, {{ scalar_t }}, {{ vec2_t }}) {
+        use crate::{{ scalar_t }}::math;
+        let det = self.matrix2.determinant();
+        glam_assert!(det != 0.0);
+
+        let scale = {{ col_t }}::new(
+            self.matrix2.x_axis.length() * math::signum(det),
+            self.matrix2.y_axis.length(),
+        );
+
+        glam_assert!(scale.cmpne({{ col_t }}::ZERO).all());
+
+        let angle = math::atan2(-self.matrix2.y_axis.x, self.matrix2.y_axis.y);
+
+        (scale, angle, self.translation)
+    }
+
     /// Transforms the given 2D point, applying shear, scale, rotation and translation.
     #[inline]
     pub fn transform_point2(&self, rhs: {{ vec2_t }}) -> {{ vec2_t }} {
@@ -700,6 +727,13 @@ impl Mul for {{ self_t }} {
     }
 }
 
+impl MulAssign for {{ self_t }} {
+    #[inline]
+    fn mul_assign(&mut self, rhs: {{ self_t }}) {
+        *self = self.mul(rhs);
+    }
+}
+
 {% if dim == 2 %}
 impl From<{{ self_t }}> for {{ mat3_t }} {
     #[inline]
diff --git a/codegen/templates/vec.rs.tera b/codegen/templates/vec.rs.tera
index 694fae5..63363f1 100644
--- a/codegen/templates/vec.rs.tera
+++ b/codegen/templates/vec.rs.tera
@@ -449,7 +449,7 @@ impl {{ self_t }} {
         self.xy()
     }
 {% elif dim == 4 %}
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`{{ vec3_t }}`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
 {%- if scalar_t == "f32" %}
@@ -1727,6 +1727,112 @@ impl {{ self_t }} {
     }
     {% endif %}
 {% endif %}
+
+{% if not is_float %}
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.wrapping_add(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.wrapping_sub(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.wrapping_mul(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.wrapping_div(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.saturating_add(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.saturating_sub(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.saturating_mul(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            {% for c in components %}
+                {{ c }}: self.{{ c }}.saturating_div(rhs.{{ c }}),
+            {%- endfor %}
+        }
+    }
+{% endif %}
 }
 
 impl Default for {{ self_t }} {
diff --git a/src/euler.rs b/src/euler.rs
index bb9236b..85f8099 100644
--- a/src/euler.rs
+++ b/src/euler.rs
@@ -46,9 +46,10 @@ pub(crate) trait EulerFromQuaternion<Q: Copy>: Sized + Copy {
     fn third(self, q: Q) -> Self::Output;
 
     /// Compute all angles of a rotation in the notation order
-    fn convert_quat(self, q: Q) -> (Self::Output, Self::Output, Self::Output) {
-        (self.first(q), self.second(q), self.third(q))
-    }
+    fn convert_quat(self, q: Q) -> (Self::Output, Self::Output, Self::Output);
+
+    #[doc(hidden)]
+    fn sine_theta(self, q: Q) -> Self::Output;
 }
 
 /// Conversion from euler angles to quaternion.
@@ -62,10 +63,127 @@ macro_rules! impl_from_quat {
     ($t:ident, $quat:ident) => {
         impl EulerFromQuaternion<$quat> for EulerRot {
             type Output = $t;
+
+            fn sine_theta(self, q: $quat) -> $t {
+                use EulerRot::*;
+                match self {
+                    ZYX => -2.0 * (q.x * q.z - q.w * q.y),
+                    ZXY => 2.0 * (q.y * q.z + q.w * q.x),
+                    YXZ => -2.0 * (q.y * q.z - q.w * q.x),
+                    YZX => 2.0 * (q.x * q.y + q.w * q.z),
+                    XYZ => 2.0 * (q.x * q.z + q.w * q.y),
+                    XZY => -2.0 * (q.x * q.y - q.w * q.z),
+                }
+                .clamp(-1.0, 1.0)
+            }
+
             fn first(self, q: $quat) -> $t {
                 use crate::$t::math;
                 use EulerRot::*;
-                match self {
+
+                let sine_theta = self.sine_theta(q);
+                if math::abs(sine_theta) > 0.99999 {
+                    let scale = 2.0 * math::signum(sine_theta);
+
+                    match self {
+                        ZYX => scale * math::atan2(-q.x, q.w),
+                        ZXY => scale * math::atan2(q.y, q.w),
+                        YXZ => scale * math::atan2(-q.z, q.w),
+                        YZX => scale * math::atan2(q.x, q.w),
+                        XYZ => scale * math::atan2(q.z, q.w),
+                        XZY => scale * math::atan2(-q.y, q.w),
+                    }
+                } else {
+                    match self {
+                        ZYX => math::atan2(
+                            2.0 * (q.x * q.y + q.w * q.z),
+                            q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
+                        ),
+                        ZXY => math::atan2(
+                            -2.0 * (q.x * q.y - q.w * q.z),
+                            q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
+                        ),
+                        YXZ => math::atan2(
+                            2.0 * (q.x * q.z + q.w * q.y),
+                            q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
+                        ),
+                        YZX => math::atan2(
+                            -2.0 * (q.x * q.z - q.w * q.y),
+                            q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
+                        ),
+                        XYZ => math::atan2(
+                            -2.0 * (q.y * q.z - q.w * q.x),
+                            q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
+                        ),
+                        XZY => math::atan2(
+                            2.0 * (q.y * q.z + q.w * q.x),
+                            q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
+                        ),
+                    }
+                }
+            }
+
+            fn second(self, q: $quat) -> $t {
+                use crate::$t::math;
+                math::asin(self.sine_theta(q))
+            }
+
+            fn third(self, q: $quat) -> $t {
+                use crate::$t::math;
+                use EulerRot::*;
+                if math::abs(self.sine_theta(q)) > 0.99999 {
+                    0.0
+                } else {
+                    match self {
+                        ZYX => math::atan2(
+                            2.0 * (q.y * q.z + q.w * q.x),
+                            q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
+                        ),
+                        ZXY => math::atan2(
+                            -2.0 * (q.x * q.z - q.w * q.y),
+                            q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
+                        ),
+                        YXZ => math::atan2(
+                            2.0 * (q.x * q.y + q.w * q.z),
+                            q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
+                        ),
+                        YZX => math::atan2(
+                            -2.0 * (q.y * q.z - q.w * q.x),
+                            q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
+                        ),
+                        XYZ => math::atan2(
+                            -2.0 * (q.x * q.y - q.w * q.z),
+                            q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
+                        ),
+                        XZY => math::atan2(
+                            2.0 * (q.x * q.z + q.w * q.y),
+                            q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
+                        ),
+                    }
+                }
+            }
+
+            fn convert_quat(self, q: $quat) -> ($t, $t, $t) {
+                use crate::$t::math;
+                use EulerRot::*;
+
+                let sine_theta = self.sine_theta(q);
+                let second = math::asin(sine_theta);
+
+                if math::abs(sine_theta) > 0.99999 {
+                    let scale = 2.0 * math::signum(sine_theta);
+
+                    return match self {
+                        ZYX => (scale * math::atan2(-q.x, q.w), second, 0.0),
+                        ZXY => (scale * math::atan2(q.y, q.w), second, 0.0),
+                        YXZ => (scale * math::atan2(-q.z, q.w), second, 0.0),
+                        YZX => (scale * math::atan2(q.x, q.w), second, 0.0),
+                        XYZ => (scale * math::atan2(q.z, q.w), second, 0.0),
+                        XZY => (scale * math::atan2(-q.y, q.w), second, 0.0),
+                    };
+                }
+
+                let first = match self {
                     ZYX => math::atan2(
                         2.0 * (q.x * q.y + q.w * q.z),
                         q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
@@ -90,26 +208,9 @@ macro_rules! impl_from_quat {
                         2.0 * (q.y * q.z + q.w * q.x),
                         q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
                     ),
-                }
-            }
+                };
 
-            fn second(self, q: $quat) -> $t {
-                use crate::$t::math;
-                use EulerRot::*;
-                match self {
-                    ZYX => math::asin_clamped(-2.0 * (q.x * q.z - q.w * q.y)),
-                    ZXY => math::asin_clamped(2.0 * (q.y * q.z + q.w * q.x)),
-                    YXZ => math::asin_clamped(-2.0 * (q.y * q.z - q.w * q.x)),
-                    YZX => math::asin_clamped(2.0 * (q.x * q.y + q.w * q.z)),
-                    XYZ => math::asin_clamped(2.0 * (q.x * q.z + q.w * q.y)),
-                    XZY => math::asin_clamped(-2.0 * (q.x * q.y - q.w * q.z)),
-                }
-            }
-
-            fn third(self, q: $quat) -> $t {
-                use crate::$t::math;
-                use EulerRot::*;
-                match self {
+                let third = match self {
                     ZYX => math::atan2(
                         2.0 * (q.y * q.z + q.w * q.x),
                         q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
@@ -134,7 +235,9 @@ macro_rules! impl_from_quat {
                         2.0 * (q.x * q.z + q.w * q.y),
                         q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
                     ),
-                }
+                };
+
+                (first, second, third)
             }
         }
         // End - impl EulerFromQuaternion
diff --git a/src/f32/affine2.rs b/src/f32/affine2.rs
index 59c3baf..8c0b53c 100644
--- a/src/f32/affine2.rs
+++ b/src/f32/affine2.rs
@@ -1,7 +1,7 @@
 // Generated from affine.rs.tera template. Edit the template, not the generated file.
 
 use crate::{Mat2, Mat3, Mat3A, Vec2, Vec3A};
-use core::ops::{Deref, DerefMut, Mul};
+use core::ops::{Deref, DerefMut, Mul, MulAssign};
 
 /// A 2D affine transform, which can represent translation, rotation, scaling and shear.
 #[derive(Copy, Clone)]
@@ -206,6 +206,33 @@ impl Affine2 {
         }
     }
 
+    /// Extracts `scale`, `angle` and `translation` from `self`.
+    ///
+    /// The transform is expected to be non-degenerate and without shearing, or the output
+    /// will be invalid.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the determinant `self.matrix2` is zero or if the resulting scale
+    /// vector contains any zero elements when `glam_assert` is enabled.
+    #[inline]
+    pub fn to_scale_angle_translation(self) -> (Vec2, f32, Vec2) {
+        use crate::f32::math;
+        let det = self.matrix2.determinant();
+        glam_assert!(det != 0.0);
+
+        let scale = Vec2::new(
+            self.matrix2.x_axis.length() * math::signum(det),
+            self.matrix2.y_axis.length(),
+        );
+
+        glam_assert!(scale.cmpne(Vec2::ZERO).all());
+
+        let angle = math::atan2(-self.matrix2.y_axis.x, self.matrix2.y_axis.y);
+
+        (scale, angle, self.translation)
+    }
+
     /// Transforms the given 2D point, applying shear, scale, rotation and translation.
     #[inline]
     pub fn transform_point2(&self, rhs: Vec2) -> Vec2 {
@@ -339,6 +366,13 @@ impl Mul for Affine2 {
     }
 }
 
+impl MulAssign for Affine2 {
+    #[inline]
+    fn mul_assign(&mut self, rhs: Affine2) {
+        *self = self.mul(rhs);
+    }
+}
+
 impl From<Affine2> for Mat3 {
     #[inline]
     fn from(m: Affine2) -> Mat3 {
diff --git a/src/f32/affine3a.rs b/src/f32/affine3a.rs
index 995b649..15ff1be 100644
--- a/src/f32/affine3a.rs
+++ b/src/f32/affine3a.rs
@@ -1,7 +1,7 @@
 // Generated from affine.rs.tera template. Edit the template, not the generated file.
 
 use crate::{Mat3, Mat3A, Mat4, Quat, Vec3, Vec3A};
-use core::ops::{Deref, DerefMut, Mul};
+use core::ops::{Deref, DerefMut, Mul, MulAssign};
 
 /// A 3D affine transform, which can represent translation, rotation, scaling and shear.
 ///
@@ -499,6 +499,13 @@ impl Mul for Affine3A {
     }
 }
 
+impl MulAssign for Affine3A {
+    #[inline]
+    fn mul_assign(&mut self, rhs: Affine3A) {
+        *self = self.mul(rhs);
+    }
+}
+
 impl From<Affine3A> for Mat4 {
     #[inline]
     fn from(m: Affine3A) -> Mat4 {
diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs
index ae819d0..33354b8 100644
--- a/src/f32/coresimd/vec4.rs
+++ b/src/f32/coresimd/vec4.rs
@@ -134,7 +134,7 @@ impl Vec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     ///
diff --git a/src/f32/math.rs b/src/f32/math.rs
index b6d5f1f..3e25875 100644
--- a/src/f32/math.rs
+++ b/src/f32/math.rs
@@ -45,8 +45,8 @@ mod libm_math {
     }
 
     #[inline(always)]
-    pub(crate) fn asin_clamped(f: f32) -> f32 {
-        libm::asinf(f.clamp(-1.0, 1.0))
+    pub(crate) fn asin(f: f32) -> f32 {
+        libm::asinf(f)
     }
 
     #[inline(always)]
@@ -158,8 +158,8 @@ mod std_math {
     }
 
     #[inline(always)]
-    pub(crate) fn asin_clamped(f: f32) -> f32 {
-        f32::asin(f32::clamp(f, -1.0, 1.0))
+    pub(crate) fn asin(f: f32) -> f32 {
+        f32::asin(f)
     }
 
     #[inline(always)]
diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs
index 77b482a..9b0280b 100644
--- a/src/f32/scalar/vec4.rs
+++ b/src/f32/scalar/vec4.rs
@@ -153,7 +153,7 @@ impl Vec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     ///
diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs
index f388098..8660a32 100644
--- a/src/f32/sse2/vec4.rs
+++ b/src/f32/sse2/vec4.rs
@@ -147,7 +147,7 @@ impl Vec4 {
         }
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     ///
diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs
index 7f3e0dd..1fa9c48 100644
--- a/src/f32/wasm32/vec4.rs
+++ b/src/f32/wasm32/vec4.rs
@@ -139,7 +139,7 @@ impl Vec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     ///
diff --git a/src/f64/daffine2.rs b/src/f64/daffine2.rs
index 2dc8756..b459f79 100644
--- a/src/f64/daffine2.rs
+++ b/src/f64/daffine2.rs
@@ -1,7 +1,7 @@
 // Generated from affine.rs.tera template. Edit the template, not the generated file.
 
 use crate::{DMat2, DMat3, DVec2};
-use core::ops::{Deref, DerefMut, Mul};
+use core::ops::{Deref, DerefMut, Mul, MulAssign};
 
 /// A 2D affine transform, which can represent translation, rotation, scaling and shear.
 #[derive(Copy, Clone)]
@@ -196,6 +196,33 @@ impl DAffine2 {
         }
     }
 
+    /// Extracts `scale`, `angle` and `translation` from `self`.
+    ///
+    /// The transform is expected to be non-degenerate and without shearing, or the output
+    /// will be invalid.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the determinant `self.matrix2` is zero or if the resulting scale
+    /// vector contains any zero elements when `glam_assert` is enabled.
+    #[inline]
+    pub fn to_scale_angle_translation(self) -> (DVec2, f64, DVec2) {
+        use crate::f64::math;
+        let det = self.matrix2.determinant();
+        glam_assert!(det != 0.0);
+
+        let scale = DVec2::new(
+            self.matrix2.x_axis.length() * math::signum(det),
+            self.matrix2.y_axis.length(),
+        );
+
+        glam_assert!(scale.cmpne(DVec2::ZERO).all());
+
+        let angle = math::atan2(-self.matrix2.y_axis.x, self.matrix2.y_axis.y);
+
+        (scale, angle, self.translation)
+    }
+
     /// Transforms the given 2D point, applying shear, scale, rotation and translation.
     #[inline]
     pub fn transform_point2(&self, rhs: DVec2) -> DVec2 {
@@ -329,6 +356,13 @@ impl Mul for DAffine2 {
     }
 }
 
+impl MulAssign for DAffine2 {
+    #[inline]
+    fn mul_assign(&mut self, rhs: DAffine2) {
+        *self = self.mul(rhs);
+    }
+}
+
 impl From<DAffine2> for DMat3 {
     #[inline]
     fn from(m: DAffine2) -> DMat3 {
diff --git a/src/f64/daffine3.rs b/src/f64/daffine3.rs
index d0aadfb..41be08e 100644
--- a/src/f64/daffine3.rs
+++ b/src/f64/daffine3.rs
@@ -1,7 +1,7 @@
 // Generated from affine.rs.tera template. Edit the template, not the generated file.
 
 use crate::{DMat3, DMat4, DQuat, DVec3};
-use core::ops::{Deref, DerefMut, Mul};
+use core::ops::{Deref, DerefMut, Mul, MulAssign};
 
 /// A 3D affine transform, which can represent translation, rotation, scaling and shear.
 #[derive(Copy, Clone)]
@@ -486,6 +486,13 @@ impl Mul for DAffine3 {
     }
 }
 
+impl MulAssign for DAffine3 {
+    #[inline]
+    fn mul_assign(&mut self, rhs: DAffine3) {
+        *self = self.mul(rhs);
+    }
+}
+
 impl From<DAffine3> for DMat4 {
     #[inline]
     fn from(m: DAffine3) -> DMat4 {
diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs
index 03bb390..3ff87b5 100644
--- a/src/f64/dvec4.rs
+++ b/src/f64/dvec4.rs
@@ -147,7 +147,7 @@ impl DVec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`DVec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     #[inline]
diff --git a/src/f64/math.rs b/src/f64/math.rs
index d851ef4..45ace5c 100644
--- a/src/f64/math.rs
+++ b/src/f64/math.rs
@@ -11,8 +11,8 @@ mod libm_math {
     }
 
     #[inline(always)]
-    pub(crate) fn asin_clamped(f: f64) -> f64 {
-        libm::asin(f.clamp(-1.0, 1.0))
+    pub(crate) fn asin(f: f64) -> f64 {
+        libm::asin(f)
     }
 
     #[inline(always)]
@@ -123,8 +123,8 @@ mod std_math {
     }
 
     #[inline(always)]
-    pub(crate) fn asin_clamped(f: f64) -> f64 {
-        f64::asin(f64::clamp(f, -1.0, 1.0))
+    pub(crate) fn asin(f: f64) -> f64 {
+        f64::asin(f)
     }
 
     #[inline(always)]
diff --git a/src/i32/ivec2.rs b/src/i32/ivec2.rs
index fb736da..3aead83 100644
--- a/src/i32/ivec2.rs
+++ b/src/i32/ivec2.rs
@@ -366,6 +366,102 @@ impl IVec2 {
     pub fn as_u64vec2(&self) -> crate::U64Vec2 {
         crate::U64Vec2::new(self.x as u64, self.y as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+        }
+    }
 }
 
 impl Default for IVec2 {
diff --git a/src/i32/ivec3.rs b/src/i32/ivec3.rs
index 9ebb6a1..d50eedb 100644
--- a/src/i32/ivec3.rs
+++ b/src/i32/ivec3.rs
@@ -393,6 +393,110 @@ impl IVec3 {
     pub fn as_u64vec3(&self) -> crate::U64Vec3 {
         crate::U64Vec3::new(self.x as u64, self.y as u64, self.z as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+        }
+    }
 }
 
 impl Default for IVec3 {
diff --git a/src/i32/ivec4.rs b/src/i32/ivec4.rs
index 15dede6..8e69491 100644
--- a/src/i32/ivec4.rs
+++ b/src/i32/ivec4.rs
@@ -139,7 +139,7 @@ impl IVec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`IVec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     #[inline]
@@ -415,6 +415,118 @@ impl IVec4 {
     pub fn as_u64vec4(&self) -> crate::U64Vec4 {
         crate::U64Vec4::new(self.x as u64, self.y as u64, self.z as u64, self.w as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+            w: self.w.wrapping_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+            w: self.w.wrapping_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+            w: self.w.wrapping_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+            w: self.w.wrapping_div(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+            w: self.w.saturating_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+            w: self.w.saturating_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+            w: self.w.saturating_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+            w: self.w.saturating_div(rhs.w),
+        }
+    }
 }
 
 impl Default for IVec4 {
diff --git a/src/i64/i64vec2.rs b/src/i64/i64vec2.rs
index ed16052..cb002e1 100644
--- a/src/i64/i64vec2.rs
+++ b/src/i64/i64vec2.rs
@@ -366,6 +366,102 @@ impl I64Vec2 {
     pub fn as_u64vec2(&self) -> crate::U64Vec2 {
         crate::U64Vec2::new(self.x as u64, self.y as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+        }
+    }
 }
 
 impl Default for I64Vec2 {
diff --git a/src/i64/i64vec3.rs b/src/i64/i64vec3.rs
index 7c44034..e740c33 100644
--- a/src/i64/i64vec3.rs
+++ b/src/i64/i64vec3.rs
@@ -393,6 +393,110 @@ impl I64Vec3 {
     pub fn as_u64vec3(&self) -> crate::U64Vec3 {
         crate::U64Vec3::new(self.x as u64, self.y as u64, self.z as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+        }
+    }
 }
 
 impl Default for I64Vec3 {
diff --git a/src/i64/i64vec4.rs b/src/i64/i64vec4.rs
index 173e12b..7c0a0fc 100644
--- a/src/i64/i64vec4.rs
+++ b/src/i64/i64vec4.rs
@@ -139,7 +139,7 @@ impl I64Vec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`I64Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     #[inline]
@@ -415,6 +415,118 @@ impl I64Vec4 {
     pub fn as_u64vec4(&self) -> crate::U64Vec4 {
         crate::U64Vec4::new(self.x as u64, self.y as u64, self.z as u64, self.w as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+            w: self.w.wrapping_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+            w: self.w.wrapping_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+            w: self.w.wrapping_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+            w: self.w.wrapping_div(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+            w: self.w.saturating_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+            w: self.w.saturating_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+            w: self.w.saturating_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+            w: self.w.saturating_div(rhs.w),
+        }
+    }
 }
 
 impl Default for I64Vec4 {
diff --git a/src/u32/uvec2.rs b/src/u32/uvec2.rs
index f953a87..9ad20e6 100644
--- a/src/u32/uvec2.rs
+++ b/src/u32/uvec2.rs
@@ -269,6 +269,102 @@ impl UVec2 {
     pub fn as_u64vec2(&self) -> crate::U64Vec2 {
         crate::U64Vec2::new(self.x as u64, self.y as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+        }
+    }
 }
 
 impl Default for UVec2 {
diff --git a/src/u32/uvec3.rs b/src/u32/uvec3.rs
index 896d738..5fd1a50 100644
--- a/src/u32/uvec3.rs
+++ b/src/u32/uvec3.rs
@@ -312,6 +312,110 @@ impl UVec3 {
     pub fn as_u64vec3(&self) -> crate::U64Vec3 {
         crate::U64Vec3::new(self.x as u64, self.y as u64, self.z as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+        }
+    }
 }
 
 impl Default for UVec3 {
diff --git a/src/u32/uvec4.rs b/src/u32/uvec4.rs
index 0c1df1e..60105a1 100644
--- a/src/u32/uvec4.rs
+++ b/src/u32/uvec4.rs
@@ -124,7 +124,7 @@ impl UVec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`UVec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     #[inline]
@@ -326,6 +326,118 @@ impl UVec4 {
     pub fn as_u64vec4(&self) -> crate::U64Vec4 {
         crate::U64Vec4::new(self.x as u64, self.y as u64, self.z as u64, self.w as u64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+            w: self.w.wrapping_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+            w: self.w.wrapping_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+            w: self.w.wrapping_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+            w: self.w.wrapping_div(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+            w: self.w.saturating_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+            w: self.w.saturating_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+            w: self.w.saturating_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+            w: self.w.saturating_div(rhs.w),
+        }
+    }
 }
 
 impl Default for UVec4 {
diff --git a/src/u64/u64vec2.rs b/src/u64/u64vec2.rs
index 3438dbd..a96d4ac 100644
--- a/src/u64/u64vec2.rs
+++ b/src/u64/u64vec2.rs
@@ -269,6 +269,102 @@ impl U64Vec2 {
     pub fn as_i64vec2(&self) -> crate::I64Vec2 {
         crate::I64Vec2::new(self.x as i64, self.y as i64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+        }
+    }
 }
 
 impl Default for U64Vec2 {
diff --git a/src/u64/u64vec3.rs b/src/u64/u64vec3.rs
index a53eea3..5bde114 100644
--- a/src/u64/u64vec3.rs
+++ b/src/u64/u64vec3.rs
@@ -312,6 +312,110 @@ impl U64Vec3 {
     pub fn as_i64vec3(&self) -> crate::I64Vec3 {
         crate::I64Vec3::new(self.x as i64, self.y as i64, self.z as i64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+        }
+    }
 }
 
 impl Default for U64Vec3 {
diff --git a/src/u64/u64vec4.rs b/src/u64/u64vec4.rs
index c58d9e1..12fd521 100644
--- a/src/u64/u64vec4.rs
+++ b/src/u64/u64vec4.rs
@@ -124,7 +124,7 @@ impl U64Vec4 {
         slice[3] = self.w;
     }
 
-    /// Creates a 2D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
+    /// Creates a 3D vector from the `x`, `y` and `z` elements of `self`, discarding `w`.
     ///
     /// Truncation to [`U64Vec3`] may also be performed by using [`self.xyz()`][crate::swizzles::Vec4Swizzles::xyz()].
     #[inline]
@@ -326,6 +326,118 @@ impl U64Vec4 {
     pub fn as_i64vec4(&self) -> crate::I64Vec4 {
         crate::I64Vec4::new(self.x as i64, self.y as i64, self.z as i64, self.w as i64)
     }
+
+    /// Returns a vector containing the wrapping addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_add(rhs.x), self.y.wrapping_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_add(rhs.x),
+            y: self.y.wrapping_add(rhs.y),
+            z: self.z.wrapping_add(rhs.z),
+            w: self.w.wrapping_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_sub(rhs.x), self.y.wrapping_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_sub(rhs.x),
+            y: self.y.wrapping_sub(rhs.y),
+            z: self.z.wrapping_sub(rhs.z),
+            w: self.w.wrapping_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_mul(rhs.x), self.y.wrapping_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_mul(rhs.x),
+            y: self.y.wrapping_mul(rhs.y),
+            z: self.z.wrapping_mul(rhs.z),
+            w: self.w.wrapping_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the wrapping division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.wrapping_div(rhs.x), self.y.wrapping_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn wrapping_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.wrapping_div(rhs.x),
+            y: self.y.wrapping_div(rhs.y),
+            z: self.z.wrapping_div(rhs.z),
+            w: self.w.wrapping_div(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating addition of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_add(rhs.x), self.y.saturating_add(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_add(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_add(rhs.x),
+            y: self.y.saturating_add(rhs.y),
+            z: self.z.saturating_add(rhs.z),
+            w: self.w.saturating_add(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating subtraction of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_sub(rhs.x), self.y.saturating_sub(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_sub(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_sub(rhs.x),
+            y: self.y.saturating_sub(rhs.y),
+            z: self.z.saturating_sub(rhs.z),
+            w: self.w.saturating_sub(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating multiplication of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_mul(rhs.x), self.y.saturating_mul(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_mul(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_mul(rhs.x),
+            y: self.y.saturating_mul(rhs.y),
+            z: self.z.saturating_mul(rhs.z),
+            w: self.w.saturating_mul(rhs.w),
+        }
+    }
+
+    /// Returns a vector containing the saturating division of `self` and `rhs`.
+    ///
+    /// In other words this computes `[self.x.saturating_div(rhs.x), self.y.saturating_div(rhs.y), ..]`.
+    #[inline]
+    #[must_use]
+    pub const fn saturating_div(self, rhs: Self) -> Self {
+        Self {
+            x: self.x.saturating_div(rhs.x),
+            y: self.y.saturating_div(rhs.y),
+            z: self.z.saturating_div(rhs.z),
+            w: self.w.saturating_div(rhs.w),
+        }
+    }
 }
 
 impl Default for U64Vec4 {


Test patch:
diff --git a/tests/affine2.rs b/tests/affine2.rs
index c486371..08cbd3b 100644
--- a/tests/affine2.rs
+++ b/tests/affine2.rs
@@ -147,6 +147,74 @@ macro_rules! impl_affine2_tests {
             should_glam_assert!({ $affine2::ZERO.inverse() });
         });
 
+        glam_test!(test_affine2_decompose, {
+            // identity
+            let (out_scale, out_rotation, out_translation) =
+                $affine2::IDENTITY.to_scale_angle_translation();
+            assert_approx_eq!($vec2::ONE, out_scale);
+            assert_eq!(out_rotation, 0.0);
+            assert_approx_eq!($vec2::ZERO, out_translation);
+
+            // no scale
+            let in_scale = $vec2::ONE;
+            let in_translation = $vec2::new(-2.0, 4.0);
+            let in_rotation = $t::to_radians(-45.0);
+            let in_mat =
+                $affine2::from_scale_angle_translation(in_scale, in_rotation, in_translation);
+            let (out_scale, out_rotation, out_translation) = in_mat.to_scale_angle_translation();
+            assert_approx_eq!(in_scale, out_scale, 1e-6);
+            assert_approx_eq!(in_rotation, out_rotation);
+            assert_approx_eq!(in_translation, out_translation);
+            assert_approx_eq!(
+                in_mat,
+                $affine2::from_scale_angle_translation(out_scale, out_rotation, out_translation),
+                1e-6
+            );
+
+            // positive scale
+            let in_scale = $vec2::new(1.0, 2.0);
+            let in_mat =
+                $affine2::from_scale_angle_translation(in_scale, in_rotation, in_translation);
+            let (out_scale, out_rotation, out_translation) = in_mat.to_scale_angle_translation();
+            assert_approx_eq!(in_scale, out_scale, 1e-6);
+            assert_approx_eq!(in_rotation, out_rotation);
+            assert_approx_eq!(in_translation, out_translation);
+            assert_approx_eq!(
+                in_mat,
+                $affine2::from_scale_angle_translation(out_scale, out_rotation, out_translation),
+                1e-5
+            );
+
+            // negative scale
+            let in_scale = $vec2::new(-4.0, 1.0);
+            let in_mat =
+                $affine2::from_scale_angle_translation(in_scale, in_rotation, in_translation);
+            let (out_scale, out_rotation, out_translation) = in_mat.to_scale_angle_translation();
+            assert_approx_eq!(in_scale, out_scale, 1e-6);
+            assert_approx_eq!(in_rotation, out_rotation);
+            assert_approx_eq!(in_translation, out_translation);
+            assert_approx_eq!(
+                in_mat,
+                $affine2::from_scale_angle_translation(out_scale, out_rotation, out_translation),
+                1e-5
+            );
+
+            // negative scale
+            let in_scale = $vec2::new(4.0, -1.0);
+            let in_mat =
+                $affine2::from_scale_angle_translation(in_scale, in_rotation, in_translation);
+            let (out_scale, out_rotation, out_translation) = in_mat.to_scale_angle_translation();
+            // out_scale and out_rotation are different but they produce the same matrix
+            // assert_approx_eq!(in_scale, out_scale, 1e-6);
+            // assert_approx_eq!(in_rotation, out_rotation);
+            assert_approx_eq!(in_translation, out_translation);
+            assert_approx_eq!(
+                in_mat,
+                $affine2::from_scale_angle_translation(out_scale, out_rotation, out_translation),
+                1e-6
+            );
+        });
+
         glam_test!(test_affine2_ops, {
             let m0 = $affine2::from_cols_array_2d(&MATRIX2D);
             assert_approx_eq!(m0, m0 * $affine2::IDENTITY);
diff --git a/tests/euler.rs b/tests/euler.rs
index dc4de53..b2d0726 100644
--- a/tests/euler.rs
+++ b/tests/euler.rs
@@ -1,47 +1,53 @@
 #[macro_use]
 mod support;
 
-/// Helper to calculate the inner angle in the range [0, 2*PI)
-trait AngleDiff {
-    type Output;
-    fn angle_diff(self, other: Self) -> Self::Output;
+/// Helper to get the 'canonical' version of a `Quat`. We define the canonical of quat `q` as:
+/// * `q`, if q.w > epsilon
+/// * `-q`, if q.w < -epsilon
+/// * `(0, 0, 0, 1)` otherwise
+/// The rationale is that q and -q represent the same rotation, and any (_, _, _, 0) respresent no rotation at all.
+trait CanonicalQuat: Copy {
+    fn canonical(self) -> Self;
 }
 
-macro_rules! impl_angle_diff {
-    ($t:ty, $pi:expr) => {
-        impl AngleDiff for $t {
-            type Output = $t;
-            fn angle_diff(self, other: $t) -> $t {
-                const PI2: $t = $pi + $pi;
-                let s = self.rem_euclid(PI2);
-                let o = other.rem_euclid(PI2);
-                if s > o {
-                    (s - o).min(PI2 + o - s)
-                } else {
-                    (o - s).min(PI2 + s - o)
+macro_rules! impl_canonical_quat {
+    ($t:ty) => {
+        impl CanonicalQuat for $t {
+            fn canonical(self) -> Self {
+                match self {
+                    _ if self.w >= 1e-5 => self,
+                    _ if self.w <= -1e-5 => -self,
+                    _ => <$t>::from_xyzw(0.0, 0.0, 0.0, 1.0),
                 }
             }
         }
     };
 }
-impl_angle_diff!(f32, std::f32::consts::PI);
-impl_angle_diff!(f64, std::f64::consts::PI);
-
-macro_rules! assert_approx_angle {
-    ($a:expr, $b:expr, $eps:expr) => {{
-        let (a, b) = ($a, $b);
-        let eps = $eps;
-        let diff = a.angle_diff(b);
-        assert!(
-            diff < $eps,
-            "assertion failed: `(left !== right)` \
-             (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)",
-            a,
-            b,
-            eps,
-            diff
-        );
-    }};
+
+impl_canonical_quat!(glam::Quat);
+impl_canonical_quat!(glam::DQuat);
+
+/// Helper to set some alternative epsilons based on the floating point type used
+trait EulerEpsilon {
+    /// epsilon for comparing quaterions built from eulers and axis-angles
+    const Q_EPS: f32;
+
+    /// epsilon for comparing quaternion round-tripped through eulers (quat -> euler -> quat)
+    const E_EPS: f32;
+}
+impl EulerEpsilon for f32 {
+    const Q_EPS: f32 = 1e-5;
+
+    // The scalar-math and wasm paths seems to use a particularly bad implementation of the trig functions
+    #[cfg(any(feature = "scalar-math", target_arch = "wasm32"))]
+    const E_EPS: f32 = 2e-4;
+
+    #[cfg(not(any(feature = "scalar-math", target_arch = "wasm32")))]
+    const E_EPS: f32 = 1e-5;
+}
+impl EulerEpsilon for f64 {
+    const Q_EPS: f32 = 1e-8;
+    const E_EPS: f32 = 1e-8;
 }
 
 macro_rules! impl_3axis_test {
@@ -49,9 +55,9 @@ macro_rules! impl_3axis_test {
         glam_test!($name, {
             let euler = $euler;
             assert!($U != $W); // First and last axis must be different for three axis
-            for u in (-176..=176).step_by(44) {
-                for v in (-88..=88).step_by(44) {
-                    for w in (-176..=176).step_by(44) {
+            for u in (-180..=180).step_by(15) {
+                for v in (-180..=180).step_by(15) {
+                    for w in (-180..=180).step_by(15) {
                         let u1 = (u as $t).to_radians();
                         let v1 = (v as $t).to_radians();
                         let w1 = (w as $t).to_radians();
@@ -63,19 +69,21 @@ macro_rules! impl_3axis_test {
 
                         // Test if the rotation is the expected
                         let q2: $quat = $quat::from_euler(euler, u1, v1, w1).normalize();
-                        assert_approx_eq!(q1, q2, 1e-5);
+                        assert_approx_eq!(q1.canonical(), q2.canonical(), <$t>::Q_EPS);
 
-                        // Test angle reconstruction
-                        let (u2, v2, w2) = q1.to_euler(euler);
+                        // Test quat reconstruction from angles
+                        let (u2, v2, w2) = q2.to_euler(euler);
                         let q3 = $quat::from_euler(euler, u2, v2, w2).normalize();
-
-                        assert_approx_angle!(u1, u2, 1e-4 as $t);
-                        assert_approx_angle!(v1, v2, 1e-4 as $t);
-                        assert_approx_angle!(w1, w2, 1e-4 as $t);
-
-                        assert_approx_eq!(q1 * $vec::X, q3 * $vec::X, 1e-4);
-                        assert_approx_eq!(q1 * $vec::Y, q3 * $vec::Y, 1e-4);
-                        assert_approx_eq!(q1 * $vec::Z, q3 * $vec::Z, 1e-4);
+                        assert_approx_eq!(
+                            q2.canonical(),
+                            q3.canonical(),
+                            <$t>::E_EPS,
+                            format!(
+                                "angles {:?} -> {:?}",
+                                (u, v, w),
+                                (u2.to_degrees(), v2.to_degrees(), w2.to_degrees())
+                            )
+                        );
                     }
                 }
             }
@@ -95,7 +103,7 @@ macro_rules! impl_all_quat_tests_three_axis {
 }
 
 mod euler {
-    use super::AngleDiff;
+    use super::{CanonicalQuat, EulerEpsilon};
     use glam::*;
     type ER = EulerRot;
 
diff --git a/tests/support/macros.rs b/tests/support/macros.rs
index 9801902..e7fadd1 100644
--- a/tests/support/macros.rs
+++ b/tests/support/macros.rs
@@ -56,6 +56,22 @@ macro_rules! assert_approx_eq {
             a.abs_diff(b)
         );
     }};
+    ($a:expr, $b:expr, $eps:expr, $ctx:expr) => {{
+        use $crate::support::FloatCompare;
+        let (a, b) = (&$a, &$b);
+        let eps = $eps;
+        assert!(
+            a.approx_eq(b, $eps),
+            "assertion failed: `(left !== right)` \
+             (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`), \
+             additional context: {}",
+            *a,
+            *b,
+            eps,
+            a.abs_diff(b),
+            $ctx
+        );
+    }};
 }
 
 /// Test vector normalization for float vector
diff --git a/tests/vec2.rs b/tests/vec2.rs
index ae4d07d..a0c215b 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -1160,6 +1160,62 @@ mod ivec2 {
         assert!(IVec2::try_from(U64Vec2::new(1, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            IVec2::new(i32::MAX, 5).wrapping_add(IVec2::new(1, 3)),
+            IVec2::new(i32::MIN, 8),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            IVec2::new(i32::MAX, 5).wrapping_sub(IVec2::new(1, 3)),
+            IVec2::new(2147483646, 2)
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            IVec2::new(i32::MAX, 5).wrapping_mul(IVec2::new(3, 3)),
+            IVec2::new(2147483645, 15)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            IVec2::new(i32::MAX, 5).wrapping_div(IVec2::new(3, 3)),
+            IVec2::new(715827882, 1)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            IVec2::new(i32::MAX, i32::MIN,).saturating_add(IVec2::new(1, -1)),
+            IVec2::new(i32::MAX, i32::MIN)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            IVec2::new(i32::MIN, i32::MAX).saturating_sub(IVec2::new(1, -1)),
+            IVec2::new(i32::MIN, i32::MAX)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            IVec2::new(i32::MAX, i32::MIN).saturating_mul(IVec2::new(2, 2)),
+            IVec2::new(i32::MAX, i32::MIN)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            IVec2::new(i32::MAX, i32::MIN).saturating_div(IVec2::new(2, 2)),
+            IVec2::new(1073741823, -1073741824)
+        );
+    });
+
     impl_vec2_signed_integer_tests!(i32, ivec2, IVec2, IVec3, BVec2);
     impl_vec2_eq_hash_tests!(i32, ivec2);
 
@@ -1207,6 +1263,62 @@ mod uvec2 {
         assert!(UVec2::try_from(U64Vec2::new(1, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            UVec2::new(u32::MAX, 5).wrapping_add(UVec2::new(1, 3)),
+            UVec2::new(0, 8),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            UVec2::new(u32::MAX, 5).wrapping_sub(UVec2::new(1, 3)),
+            UVec2::new(4294967294, 2)
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            UVec2::new(u32::MAX, 5).wrapping_mul(UVec2::new(3, 3)),
+            UVec2::new(4294967293, 15)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            UVec2::new(u32::MAX, 5).wrapping_div(UVec2::new(3, 3)),
+            UVec2::new(1431655765, 1)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            UVec2::new(u32::MAX, u32::MAX).saturating_add(UVec2::new(1, u32::MAX)),
+            UVec2::new(u32::MAX, u32::MAX)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            UVec2::new(0, u32::MAX).saturating_sub(UVec2::new(1, 1)),
+            UVec2::new(0, 4294967294)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            UVec2::new(u32::MAX, u32::MAX).saturating_mul(UVec2::new(2, u32::MAX)),
+            UVec2::new(u32::MAX, u32::MAX)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            UVec2::new(u32::MAX, u32::MAX).saturating_div(UVec2::new(2, u32::MAX)),
+            UVec2::new(2147483647, 1)
+        );
+    });
+
     impl_vec2_tests!(u32, uvec2, UVec2, UVec3, BVec2);
     impl_vec2_eq_hash_tests!(u32, uvec2);
 
diff --git a/tests/vec3.rs b/tests/vec3.rs
index b494f47..1af6348 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -1416,6 +1416,62 @@ mod ivec3 {
         assert!(IVec3::try_from(U64Vec3::new(1, 2, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            IVec3::new(i32::MAX, 5, i32::MIN).wrapping_add(IVec3::new(1, 3, i32::MAX)),
+            IVec3::new(i32::MIN, 8, -1),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            IVec3::new(i32::MAX, 5, i32::MIN).wrapping_sub(IVec3::new(1, 3, i32::MAX)),
+            IVec3::new(2147483646, 2, 1),
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            IVec3::new(i32::MAX, 5, i32::MIN).wrapping_mul(IVec3::new(3, 3, 5)),
+            IVec3::new(2147483645, 15, -2147483648)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            IVec3::new(i32::MAX, 5, i32::MIN).wrapping_div(IVec3::new(3, 3, 5)),
+            IVec3::new(715827882, 1, -429496729)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            IVec3::new(i32::MAX, i32::MIN, 0).saturating_add(IVec3::new(1, -1, 2)),
+            IVec3::new(i32::MAX, i32::MIN, 2)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            IVec3::new(i32::MIN, i32::MAX, 0).saturating_sub(IVec3::new(1, -1, 2)),
+            IVec3::new(i32::MIN, i32::MAX, -2)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            IVec3::new(i32::MAX, i32::MIN, 0).saturating_mul(IVec3::new(2, 2, 0)),
+            IVec3::new(i32::MAX, i32::MIN, 0)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            IVec3::new(i32::MAX, i32::MIN, 0).saturating_div(IVec3::new(2, 2, 3)),
+            IVec3::new(1073741823, -1073741824, 0)
+        );
+    });
+
     impl_vec3_signed_integer_tests!(i32, ivec3, IVec3, BVec3);
     impl_vec3_eq_hash_tests!(i32, ivec3);
 
@@ -1467,6 +1523,62 @@ mod uvec3 {
         assert!(UVec3::try_from(U64Vec3::new(1, 2, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            UVec3::new(u32::MAX, 5, u32::MAX).wrapping_add(UVec3::new(1, 3, u32::MAX)),
+            UVec3::new(0, 8, 4294967294),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            UVec3::new(u32::MAX, 5, u32::MAX - 1).wrapping_sub(UVec3::new(1, 3, u32::MAX)),
+            UVec3::new(4294967294, 2, 4294967295)
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            UVec3::new(u32::MAX, 5, u32::MAX).wrapping_mul(UVec3::new(3, 3, 5)),
+            UVec3::new(4294967293, 15, 4294967291)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            UVec3::new(u32::MAX, 5, u32::MAX).wrapping_div(UVec3::new(3, 3, 5)),
+            UVec3::new(1431655765, 1, 858993459)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            UVec3::new(u32::MAX, u32::MAX, 0).saturating_add(UVec3::new(1, u32::MAX, 2)),
+            UVec3::new(u32::MAX, u32::MAX, 2)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            UVec3::new(0, u32::MAX, 0).saturating_sub(UVec3::new(1, 1, 2)),
+            UVec3::new(0, 4294967294, 0)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            UVec3::new(u32::MAX, u32::MAX, 0).saturating_mul(UVec3::new(2, u32::MAX, 0)),
+            UVec3::new(u32::MAX, u32::MAX, 0)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            UVec3::new(u32::MAX, u32::MAX, 0).saturating_div(UVec3::new(2, u32::MAX, 3)),
+            UVec3::new(2147483647, 1, 0)
+        );
+    });
+
     impl_vec3_tests!(u32, uvec3, UVec3, BVec3);
     impl_vec3_eq_hash_tests!(u32, uvec3);
 
diff --git a/tests/vec4.rs b/tests/vec4.rs
index 8cefbca..acea048 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -1270,6 +1270,7 @@ macro_rules! impl_vec4_bit_op_tests {
         });
     };
 }
+
 mod vec4 {
     #[cfg(any(
         not(any(target_feature = "sse2", target_feature = "simd128")),
@@ -1547,6 +1548,62 @@ mod ivec4 {
         assert!(IVec4::try_from(U64Vec4::new(1, 2, 3, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            IVec4::new(i32::MAX, 5, i32::MIN, 0).wrapping_add(IVec4::new(1, 3, i32::MAX, 0)),
+            IVec4::new(i32::MIN, 8, -1, 0),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            IVec4::new(i32::MAX, 5, i32::MIN, 0).wrapping_sub(IVec4::new(1, 3, i32::MAX, 0)),
+            IVec4::new(2147483646, 2, 1, 0)
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            IVec4::new(i32::MAX, 5, i32::MIN, 0).wrapping_mul(IVec4::new(3, 3, 5, 1)),
+            IVec4::new(2147483645, 15, -2147483648, 0)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            IVec4::new(i32::MAX, 5, i32::MIN, 0).wrapping_div(IVec4::new(3, 3, 5, 1)),
+            IVec4::new(715827882, 1, -429496729, 0)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            IVec4::new(i32::MAX, i32::MIN, 0, 0).saturating_add(IVec4::new(1, -1, 2, 3)),
+            IVec4::new(i32::MAX, i32::MIN, 2, 3)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            IVec4::new(i32::MIN, i32::MAX, 0, 0).saturating_sub(IVec4::new(1, -1, 2, 3)),
+            IVec4::new(i32::MIN, i32::MAX, -2, -3)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            IVec4::new(i32::MAX, i32::MIN, 0, 0).saturating_mul(IVec4::new(2, 2, 0, 0)),
+            IVec4::new(i32::MAX, i32::MIN, 0, 0)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            IVec4::new(i32::MAX, i32::MIN, 0, 0).saturating_div(IVec4::new(2, 2, 3, 4)),
+            IVec4::new(1073741823, -1073741824, 0, 0)
+        );
+    });
+
     impl_vec4_signed_integer_tests!(i32, ivec4, IVec4, IVec3, IVec2, BVec4);
     impl_vec4_eq_hash_tests!(i32, ivec4);
 
@@ -1605,6 +1662,62 @@ mod uvec4 {
         assert!(UVec4::try_from(U64Vec4::new(1, 2, 3, u64::MAX)).is_err());
     });
 
+    glam_test!(test_wrapping_add, {
+        assert_eq!(
+            UVec4::new(u32::MAX, 5, u32::MAX, 0).wrapping_add(UVec4::new(1, 3, u32::MAX, 0)),
+            UVec4::new(0, 8, 4294967294, 0),
+        );
+    });
+
+    glam_test!(test_wrapping_sub, {
+        assert_eq!(
+            UVec4::new(u32::MAX, 5, u32::MAX - 1, 0).wrapping_sub(UVec4::new(1, 3, u32::MAX, 0)),
+            UVec4::new(4294967294, 2, 4294967295, 0)
+        );
+    });
+
+    glam_test!(test_wrapping_mul, {
+        assert_eq!(
+            UVec4::new(u32::MAX, 5, u32::MAX, 0).wrapping_mul(UVec4::new(3, 3, 5, 1)),
+            UVec4::new(4294967293, 15, 4294967291, 0)
+        );
+    });
+
+    glam_test!(test_wrapping_div, {
+        assert_eq!(
+            UVec4::new(u32::MAX, 5, u32::MAX, 0).wrapping_div(UVec4::new(3, 3, 5, 1)),
+            UVec4::new(1431655765, 1, 858993459, 0)
+        );
+    });
+
+    glam_test!(test_saturating_add, {
+        assert_eq!(
+            UVec4::new(u32::MAX, u32::MAX, 0, 0).saturating_add(UVec4::new(1, u32::MAX, 2, 3)),
+            UVec4::new(u32::MAX, u32::MAX, 2, 3)
+        );
+    });
+
+    glam_test!(test_saturating_sub, {
+        assert_eq!(
+            UVec4::new(0, u32::MAX, 0, 0).saturating_sub(UVec4::new(1, 1, 2, 3)),
+            UVec4::new(0, 4294967294, 0, 0)
+        );
+    });
+
+    glam_test!(test_saturating_mul, {
+        assert_eq!(
+            UVec4::new(u32::MAX, u32::MAX, 0, 0).saturating_mul(UVec4::new(2, u32::MAX, 0, 0)),
+            UVec4::new(u32::MAX, u32::MAX, 0, 0)
+        );
+    });
+
+    glam_test!(test_saturating_div, {
+        assert_eq!(
+            UVec4::new(u32::MAX, u32::MAX, 0, 0).saturating_div(UVec4::new(2, u32::MAX, 3, 4)),
+            UVec4::new(2147483647, 1, 0, 0)
+        );
+    });
+
     impl_vec4_tests!(u32, uvec4, UVec4, UVec3, UVec2, BVec4);
     impl_vec4_eq_hash_tests!(u32, uvec4);