Repository: bitshifter/glam-rs
Pull request #43 (2019-12-28 12:04:41)

Added VecN::floor and VecN::ceil (sse2 and scalr versions).

michal-z: Those functions handle INF and -INF just fine. NANs are handled differently than in scalar versions. SIMD versions still produce NANs but with different bit encoding.
So, this test fails:
assert_eq!(new::Vec3(f32::NAN, 0.0, 0.0).floor().x(), f32::NAN);

But this one passes:
assert_eq!(new::Vec3(f32::NAN, 0.0, 0.0).floor().x().is_nan(), f32::NAN.is_nan());

We still get NANs (all exponent bits are '1') but with different mantissa bits. I think it can be tricky to implement scalar versions that behaves the same.

Personally I think that we should allow this for performance reasons. What do you think?
bitshifter: It's fine if the bit pattern is different, as long as `is_nan` is consistent. Thanks for checking.
bitshifter: Did you want to add any additional test commits or just leave it?
michal-z: I will add test later today.

Patch:
diff --git a/src/f32/funcs.rs b/src/f32/funcs.rs
index 89dbca8..9ac5a0a 100644
--- a/src/f32/funcs.rs
+++ b/src/f32/funcs.rs
@@ -65,6 +65,48 @@ pub(crate) unsafe fn m128_round(v: __m128) -> __m128 {
     _mm_xor_ps(r1, r2)
 }
 
+#[cfg(all(target_feature = "sse2", not(feature = "scalar-math")))]
+#[inline]
+pub(crate) unsafe fn m128_floor(v: __m128) -> __m128 {
+    // From DirectXMath XMVectorFloor
+    // To handle NAN, INF and numbers greater than 8388608, use masking
+    let test = _mm_and_si128(_mm_castps_si128(v), PS_INV_SIGN_MASK.m128i);
+    let test = _mm_cmplt_epi32(test, PS_NO_FRACTION.m128i);
+    // Truncate
+    let vint = _mm_cvttps_epi32(v);
+    let result = _mm_cvtepi32_ps(vint);
+    let larger = _mm_cmpgt_ps(result, v);
+    // 0 -> 0, 0xffffffff -> -1.0f
+    let larger = _mm_cvtepi32_ps(_mm_castps_si128(larger));
+    let result = _mm_add_ps(result, larger);
+    // All numbers less than 8388608 will use the round to int
+    let result = _mm_and_ps(result, _mm_castsi128_ps(test));
+    // All others, use the ORIGINAL value
+    let test = _mm_andnot_si128(test, _mm_castps_si128(v));
+    _mm_or_ps(result, _mm_castsi128_ps(test))
+}
+
+#[cfg(all(target_feature = "sse2", not(feature = "scalar-math")))]
+#[inline]
+pub(crate) unsafe fn m128_ceil(v: __m128) -> __m128 {
+    // From DirectXMath XMVectorCeil
+    // To handle NAN, INF and numbers greater than 8388608, use masking
+    let test = _mm_and_si128(_mm_castps_si128(v), PS_INV_SIGN_MASK.m128i);
+    let test = _mm_cmplt_epi32(test, PS_NO_FRACTION.m128i);
+    // Truncate
+    let vint = _mm_cvttps_epi32(v);
+    let result = _mm_cvtepi32_ps(vint);
+    let smaller = _mm_cmplt_ps(result, v);
+    // 0 -> 0, 0xffffffff -> -1.0f
+    let smaller = _mm_cvtepi32_ps(_mm_castps_si128(smaller));
+    let result = _mm_sub_ps(result, smaller);
+    // All numbers less than 8388608 will use the round to int
+    let result = _mm_and_ps(result, _mm_castsi128_ps(test));
+    // All others, use the ORIGINAL value
+    let test = _mm_andnot_si128(test, _mm_castps_si128(v));
+    _mm_or_ps(result, _mm_castsi128_ps(test))
+}
+
 // Based on http://gruntthepeon.free.fr/ssemath/sse_mathfun.h
 // #[cfg(target_feature = "sse2")]
 // unsafe fn sin_cos_sse2(x: __m128) -> (__m128, __m128) {
diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs
index c795303..11842d6 100644
--- a/src/f32/vec2.rs
+++ b/src/f32/vec2.rs
@@ -320,6 +320,16 @@ impl Vec2 {
     pub fn round(self) -> Self {
         Self(self.0.round(), self.1.round())
     }
+
+    #[inline]
+    pub fn floor(self) -> Self {
+        Self(self.0.floor(), self.1.floor())
+    }
+
+    #[inline]
+    pub fn ceil(self) -> Self {
+        Self(self.0.ceil(), self.1.ceil())
+    }
 }
 
 impl fmt::Display for Vec2 {
diff --git a/src/f32/vec3_f32.rs b/src/f32/vec3_f32.rs
index e07076e..4d2bafe 100644
--- a/src/f32/vec3_f32.rs
+++ b/src/f32/vec3_f32.rs
@@ -255,6 +255,16 @@ impl Vec3 {
     pub fn round(self) -> Self {
         Self(self.0.round(), self.1.round(), self.2.round())
     }
+
+    #[inline]
+    pub fn floor(self) -> Self {
+        Self(self.0.floor(), self.1.floor(), self.2.floor())
+    }
+
+    #[inline]
+    pub fn ceil(self) -> Self {
+        Self(self.0.ceil(), self.1.ceil(), self.2.ceil())
+    }
 }
 
 impl fmt::Display for Vec3 {
diff --git a/src/f32/vec3_sse2.rs b/src/f32/vec3_sse2.rs
index 08df0a8..ed71745 100644
--- a/src/f32/vec3_sse2.rs
+++ b/src/f32/vec3_sse2.rs
@@ -6,7 +6,7 @@ use rand::{
     Rng,
 };
 
-use crate::f32::funcs::m128_round;
+use crate::f32::funcs::{m128_ceil, m128_floor, m128_round};
 use crate::{
     f32::{Vec2, Vec4, X_AXIS, Y_AXIS, Z_AXIS},
     Align16,
@@ -369,6 +369,16 @@ impl Vec3 {
     pub fn round(self) -> Self {
         unsafe { Self(m128_round(self.0)) }
     }
+
+    #[inline]
+    pub fn floor(self) -> Self {
+        unsafe { Self(m128_floor(self.0)) }
+    }
+
+    #[inline]
+    pub fn ceil(self) -> Self {
+        unsafe { Self(m128_ceil(self.0)) }
+    }
 }
 
 impl fmt::Display for Vec3 {
diff --git a/src/f32/vec4_f32.rs b/src/f32/vec4_f32.rs
index a01803a..904668d 100644
--- a/src/f32/vec4_f32.rs
+++ b/src/f32/vec4_f32.rs
@@ -368,6 +368,21 @@ impl Vec4 {
             self.3.round(),
         )
     }
+
+    #[inline]
+    pub fn floor(self) -> Self {
+        Self(
+            self.0.floor(),
+            self.1.floor(),
+            self.2.floor(),
+            self.3.floor(),
+        )
+    }
+
+    #[inline]
+    pub fn ceil(self) -> Self {
+        Self(self.0.ceil(), self.1.ceil(), self.2.ceil(), self.3.ceil())
+    }
 }
 
 impl fmt::Display for Vec4 {
diff --git a/src/f32/vec4_sse2.rs b/src/f32/vec4_sse2.rs
index 02dbb39..b8e4535 100644
--- a/src/f32/vec4_sse2.rs
+++ b/src/f32/vec4_sse2.rs
@@ -6,7 +6,7 @@ use rand::{
     Rng,
 };
 
-use crate::f32::funcs::m128_round;
+use crate::f32::funcs::{m128_ceil, m128_floor, m128_round};
 use crate::{f32::Vec3, Align16};
 
 #[cfg(target_arch = "x86")]
@@ -401,6 +401,16 @@ impl Vec4 {
     pub fn round(self) -> Self {
         unsafe { Self(m128_round(self.0)) }
     }
+
+    #[inline]
+    pub fn floor(self) -> Self {
+        unsafe { Self(m128_floor(self.0)) }
+    }
+
+    #[inline]
+    pub fn ceil(self) -> Self {
+        unsafe { Self(m128_ceil(self.0)) }
+    }
 }
 
 impl fmt::Display for Vec4 {


Test patch:
diff --git a/tests/vec2.rs b/tests/vec2.rs
index 3b770b7..8b56e7d 100644
--- a/tests/vec2.rs
+++ b/tests/vec2.rs
@@ -3,6 +3,7 @@ use glam::*;
 use rand::{Rng, SeedableRng};
 #[cfg(feature = "rand")]
 use rand_xoshiro::Xoshiro256Plus;
+use std::f32;
 
 #[test]
 fn test_vec2_align() {
@@ -321,6 +322,48 @@ fn test_vec2_round() {
     assert_eq!(Vec2::new(0.0, 21.1).round().y(), 21.0);
     assert_eq!(Vec2::new(0.0, 11.123).round().y(), 11.0);
     assert_eq!(Vec2::new(0.0, 11.499).round().y(), 11.0);
+    assert_eq!(
+        Vec2::new(f32::NEG_INFINITY, f32::INFINITY).round(),
+        Vec2::new(f32::NEG_INFINITY, f32::INFINITY)
+    );
+    assert_eq!(
+        Vec2::new(f32::NAN, 0.0).round().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+}
+
+#[test]
+fn test_vec2_floor() {
+    assert_eq!(Vec2::new(1.35, -1.5).floor(), Vec2::new(1.0, -2.0));
+    assert_eq!(
+        Vec2::new(f32::INFINITY, f32::NEG_INFINITY).floor(),
+        Vec2::new(f32::INFINITY, f32::NEG_INFINITY)
+    );
+    assert_eq!(
+        Vec2::new(f32::NAN, 0.0).floor().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec2::new(-2000000.123, 10000000.123).floor(),
+        Vec2::new(-2000001.0, 10000000.0)
+    );
+}
+
+#[test]
+fn test_vec2_ceil() {
+    assert_eq!(Vec2::new(1.35, -1.5).ceil(), Vec2::new(2.0, -1.0));
+    assert_eq!(
+        Vec2::new(f32::INFINITY, f32::NEG_INFINITY).ceil(),
+        Vec2::new(f32::INFINITY, f32::NEG_INFINITY)
+    );
+    assert_eq!(
+        Vec2::new(f32::NAN, 0.0).ceil().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec2::new(-2000000.123, 1000000.123).ceil(),
+        Vec2::new(-2000000.0, 1000001.0)
+    );
 }
 
 #[cfg(feature = "serde")]
diff --git a/tests/vec3.rs b/tests/vec3.rs
index 33cd63b..8849569 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -5,6 +5,7 @@ use glam::*;
 use rand::{Rng, SeedableRng};
 #[cfg(feature = "rand")]
 use rand_xoshiro::Xoshiro256Plus;
+use std::f32;
 
 #[test]
 fn test_vec3_align() {
@@ -366,6 +367,51 @@ fn test_vec3_round() {
     assert_eq!(Vec3::new(0.0, 21.1, 0.0).round().y(), 21.0);
     assert_eq!(Vec3::new(0.0, 11.123, 0.0).round().y(), 11.0);
     assert_eq!(Vec3::new(0.0, 11.499, 0.0).round().y(), 11.0);
+    assert_eq!(
+        Vec3::new(f32::NEG_INFINITY, f32::INFINITY, 0.0).round(),
+        Vec3::new(f32::NEG_INFINITY, f32::INFINITY, 0.0)
+    );
+    assert_eq!(
+        Vec3::new(f32::NAN, 0.0, 0.0).round().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+}
+
+#[test]
+fn test_vec3_floor() {
+    assert_eq!(
+        Vec3::new(1.35, 1.5, -1.5).floor(),
+        Vec3::new(1.0, 1.0, -2.0)
+    );
+    assert_eq!(
+        Vec3::new(f32::INFINITY, f32::NEG_INFINITY, 0.0).floor(),
+        Vec3::new(f32::INFINITY, f32::NEG_INFINITY, 0.0)
+    );
+    assert_eq!(
+        Vec3::new(f32::NAN, 0.0, 0.0).floor().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec3::new(-2000000.123, 10000000.123, 1000.9).floor(),
+        Vec3::new(-2000001.0, 10000000.0, 1000.0)
+    );
+}
+
+#[test]
+fn test_vec3_ceil() {
+    assert_eq!(Vec3::new(1.35, 1.5, -1.5).ceil(), Vec3::new(2.0, 2.0, -1.0));
+    assert_eq!(
+        Vec3::new(f32::INFINITY, f32::NEG_INFINITY, 0.0).ceil(),
+        Vec3::new(f32::INFINITY, f32::NEG_INFINITY, 0.0)
+    );
+    assert_eq!(
+        Vec3::new(f32::NAN, 0.0, 0.0).ceil().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec3::new(-2000000.123, 1000000.123, 1000.9).ceil(),
+        Vec3::new(-2000000.0, 1000001.0, 1001.0)
+    );
 }
 
 #[cfg(feature = "serde")]
diff --git a/tests/vec4.rs b/tests/vec4.rs
index da9635a..6046f4d 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -5,6 +5,7 @@ use glam::*;
 use rand::{Rng, SeedableRng};
 #[cfg(feature = "rand")]
 use rand_xoshiro::Xoshiro256Plus;
+use std::f32;
 
 #[test]
 fn test_vec4_align() {
@@ -393,6 +394,54 @@ fn test_vec4_round() {
     assert_eq!(Vec4::new(0.0, 21.1, 0.0, 0.0).round().y(), 21.0);
     assert_eq!(Vec4::new(0.0, 0.0, 0.0, 11.123).round().w(), 11.0);
     assert_eq!(Vec4::new(0.0, 0.0, 11.501, 0.0).round().z(), 12.0);
+    assert_eq!(
+        Vec4::new(f32::NEG_INFINITY, f32::INFINITY, 1.0, -1.0).round(),
+        Vec4::new(f32::NEG_INFINITY, f32::INFINITY, 1.0, -1.0)
+    );
+    assert_eq!(
+        Vec4::new(f32::NAN, 0.0, 0.0, 1.0).round().x().is_nan(),
+        f32::NAN.is_nan()
+    );
+}
+
+#[test]
+fn test_vec4_floor() {
+    assert_eq!(
+        Vec4::new(1.35, 1.5, -1.5, 1.999).floor(),
+        Vec4::new(1.0, 1.0, -2.0, 1.0)
+    );
+    assert_eq!(
+        Vec4::new(f32::INFINITY, f32::NEG_INFINITY, 0.0, 0.0).floor(),
+        Vec4::new(f32::INFINITY, f32::NEG_INFINITY, 0.0, 0.0)
+    );
+    assert_eq!(
+        Vec4::new(0.0, f32::NAN, 0.0, 0.0).floor().y().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec4::new(-0.0, -2000000.123, 10000000.123, 1000.9).floor(),
+        Vec4::new(-0.0, -2000001.0, 10000000.0, 1000.0)
+    );
+}
+
+#[test]
+fn test_vec4_ceil() {
+    assert_eq!(
+        Vec4::new(1.35, 1.5, -1.5, 1234.1234).ceil(),
+        Vec4::new(2.0, 2.0, -1.0, 1235.0)
+    );
+    assert_eq!(
+        Vec4::new(f32::INFINITY, f32::NEG_INFINITY, 0.0, 0.0).ceil(),
+        Vec4::new(f32::INFINITY, f32::NEG_INFINITY, 0.0, 0.0)
+    );
+    assert_eq!(
+        Vec4::new(0.0, 0.0, f32::NAN, 0.0).ceil().z().is_nan(),
+        f32::NAN.is_nan()
+    );
+    assert_eq!(
+        Vec4::new(-1234.1234, -2000000.123, 1000000.123, 1000.9).ceil(),
+        Vec4::new(-1234.0, -2000000.0, 1000001.0, 1001.0)
+    );
 }
 
 #[cfg(feature = "serde")]