]> git.nega.tv - josh/narcissus/commitdiff
narcissus-maths: Add additional rng functions
authorJoshua Simmons <josh@nega.tv>
Sun, 5 May 2024 14:07:04 +0000 (16:07 +0200)
committerJoshua Simmons <josh@nega.tv>
Sun, 5 May 2024 14:07:04 +0000 (16:07 +0200)
Add `next_[f32,f64]_s`, returning uniformly distibuted floats in the
range `-1.0..1.0`.
Add `next_uniform_unit_disc_f32`, generating a uniformly distributed
point on the unit disc.
Add `next_uniform_unit_circle_f32`, generating a uniformly distributed
point on the unit circle.

engine/narcissus-core/src/rand.rs

index 1ad91473aa6bde4f12ae4a4dd5c4ee537c9189b1..ff52513d68d82b03a7e66227d58419ab61c4af6b 100644 (file)
@@ -68,8 +68,8 @@ impl Pcg64 {
     #[inline]
     #[must_use]
     pub fn next_f32(&mut self) -> f32 {
-        let value = (self.next_u64() >> (64 - 24)) as i64 as f32;
-        value * 5.960_464_5e-8 // 0x1p-24f
+        let value = (self.next_u64() >> (64 - 24)) as f32;
+        value * 5.960_464_5e-8 // 0x1p-24
     }
 
     /// Generates a uniformly distributed random f64 in the range `0.0..1.0`
@@ -78,10 +78,71 @@ impl Pcg64 {
     #[inline]
     #[must_use]
     pub fn next_f64(&mut self) -> f64 {
-        let value = (self.next_u64() >> (64 - 53)) as i64 as f64;
+        let value = (self.next_u64() >> (64 - 53)) as f64;
         value * 1.110_223_024_625_156_5e-16 // 0x1p-53
     }
 
+    /// Generates a uniformly distributed random f32 in the range `-1.0..1.0`
+    ///
+    /// Always draws one 64 bit word from the PRNG.
+    #[inline]
+    #[must_use]
+    pub fn next_f32_s(&mut self) -> f32 {
+        let value = (self.next_u64() as i64 >> (64 - 25)) as f32;
+        value * 5.960_464_5e-8 // 0x1p-24
+    }
+
+    /// Generates a uniformly distributed random f64 in the range `-1.0..1.0`
+    ///
+    /// Always draws one 64 bit word from the PRNG.
+    #[inline]
+    #[must_use]
+    pub fn next_f64_s(&mut self) -> f64 {
+        let value = (self.next_u64() as i64 >> (64 - 54)) as f64;
+        value * 1.110_223_024_625_156_5e-16 // 0x1p-53
+    }
+
+    /// Generate a uniformly distributed point on the unit disc using rejection
+    /// sampling.
+    ///
+    /// Returns a tuple containing the dot product of the point with itself, as
+    /// well as the point.
+    ///
+    /// (p.p, [px, py])
+    ///
+    /// # Notes
+    ///
+    /// Uniform point on unit disc by Marc B. Reynolds:
+    /// https://marc-b-reynolds.github.io/distribution/2016/11/28/Uniform.html
+    pub fn next_uniform_unit_disc_f32(&mut self) -> (f32, [f32; 2]) {
+        let mut x;
+        let mut y;
+        let mut d;
+        loop {
+            x = self.next_f32_s();
+            y = self.next_f32_s();
+            d = x * x + y * y;
+            if d < 1.0 {
+                break;
+            }
+        }
+        (d, [x, y])
+    }
+
+    /// Generate a uniformly distributed point on the unit circle.
+    ///
+    /// # Notes
+    ///
+    /// Uniform point on unit circle by Marc B. Reynolds:
+    /// https://marc-b-reynolds.github.io/distribution/2016/11/28/Uniform.html
+    pub fn next_uniform_unit_circle_f32(&mut self) -> [f32; 2] {
+        const BIAS: f32 = 1.0 / 68719476736.0; // 0x1p-36
+        let (d, [x, y]) = self.next_uniform_unit_disc_f32();
+        let s = (d + BIAS * BIAS).sqrt().recip();
+        let x = x + BIAS;
+        [x * s, y * s]
+    }
+
     /// Randomly select an an element from `slice` with uniform probability.
     ///
     /// Always draws two 64 bit words from the PRNG.
@@ -247,4 +308,22 @@ mod tests {
             assert!(x >= 0.0 && x < 1.0);
         }
     }
+
+    #[test]
+    fn uniform_f32_s() {
+        let mut rng = Pcg64::new();
+        for _ in 0..100_000 {
+            let x = rng.next_f32_s();
+            assert!(x >= -1.0 && x < 1.0);
+        }
+    }
+
+    #[test]
+    fn uniform_f64_s() {
+        let mut rng = Pcg64::new();
+        for _ in 0..100_000 {
+            let x = rng.next_f64_s();
+            assert!(x >= -1.0 && x < 1.0);
+        }
+    }
 }