ulfben icon

Jenkins Small Fast PRNG

ulfben | PRO | 06/11/24 01:25:06 PM UTC (Edited) | 0 ⭐ | 11045 👁️ | Never ⏰ | []
C++ |

4.01 KB

|

None

|

0 👍

/

0 👎

#include <array>
#include <cstdint>
#include <cassert>
#include <limits>
#include <span>
#include <cmath>
/*
A C++ 64-bit three-rotate implementation of the famous Jenkins Small Fast PRNG.
Original public domain C-code and writeup by Bob Jenkins https://burtleburtle.net/bob/rand/smallprng.html
C++ implementation by Ulf Benjaminsson (ulfbenjaminsson.com), also placed in the public domain. Use freely!
*/
class SmallPRNG
 {
    using u64 = uint64_t;
    u64 a;
    u64 b;
    u64 c;
    u64 d;    
 
    static constexpr u64 rot(u64 x, u64 k) noexcept {
        return (x << k) | (x >> (64 - k));
    }
 
public:
    constexpr SmallPRNG(u64 seed) noexcept : a(0xf1ea5eed), b(seed), c(seed), d(seed) {        
        // warmup: run the generator a couple of cycles to mix the state thoroughly
        for (auto i = 0; i < 20; ++i) { 
            next();
        }
    }
    constexpr SmallPRNG(std::span<const u64, 4> state) noexcept : a(state[0]), b(state[1]), c(state[2]), d(state[3]) {}
 
    constexpr u64 max() noexcept{
        return std::numeric_limits<u64>::max();
    }
  
    constexpr bool coinToss() noexcept{
        return next() & 1; //checks the least significant bit
    }
 
    template<typename T>
    constexpr T between(T min, T max) noexcept {
        assert(min < max && "SmallPRNG::between(min, max) called with inverted range.");
        if constexpr (std::is_floating_point_v<T>) {
            return min + (max - min) * normalized<T>();
        } else if constexpr (std::is_integral_v<T>) {
            using UT = std::make_unsigned_t<T>;
            UT range = static_cast<UT>(max - min);
            UT num = next() % (range + 1);
            return min + static_cast<T>(num);
        }
    }
 
    template<typename T = float>
    constexpr T normalized() noexcept {
        return static_cast<T>(next()) / (static_cast<long double>(max()) + 1.0L);
    }
 
    template<typename T = float>
    constexpr T unit_range() noexcept {
        static_assert(std::is_floating_point_v<T>, "raunit_range can only be used with floating point types.");
        return static_cast<T>(2.0) * normalized<T>() - static_cast<T>(1.0);
    }
 
    constexpr u64 next() noexcept {
        // The rotate constants (7, 13, 37) are chosen specifically for 64-bit term, to provide
        // better avalanche characteristics, achieving 18.4 bits of avalanche after 5 rounds.
        const u64 e = a - rot(b, 7); 
        a = b ^ rot(c, 13); 
        b = c + rot(d, 37);
        c = d + e;
        d = e + a;
        return d;
    }
 
    template<typename T = float>
    constexpr T next_gaussian(T mean, T stddev) noexcept {
        static_assert(std::is_floating_point_v<T>, "next_guassian can only be used with a floating point type");
        static bool hasSpare = false;
        static T spare;
 
        if (hasSpare) {
            hasSpare = false;
            return mean + stddev * spare;
        }
 
        hasSpare = true;
        T u, v, s;
        do {
            u = unit_range<T>();
            v = unit_range<T>();
            s = u * u + v * v;
        } while (s >= T(1.0) || s == T(0.0));
        s = std::sqrt(T(-2.0) * std::log(s) / s);
        spare = v * s;
        return mean + stddev * (u * s);
    }
 
    constexpr std::array<u64, 4> get_state() const noexcept {
        return {a, b, c, d};
    }
    constexpr void set_state(std::span<const u64, 4> s) noexcept {
        *this = SmallPRNG(s);
    }
};
 
int main() {
    SmallPRNG rand(223456721);
 
    [[maybe_unused]] auto random_unsigned = rand.between(10u, 50u); 
    [[maybe_unused]] int random_int = rand.between(-10, 10); 
    [[maybe_unused]] float random_normalized = rand.normalized(); //0.0 - 1.0
    [[maybe_unused]] float ndc = rand.unit_range();  //-1.0 - +1.0
    //[[maybe_unused]] auto gaus = rand.next_gaussian(70.0f, 10.0f);
    [[maybe_unused]] double random_double = rand.between(1.0, 5.0); 
 
    auto state = rand.get_state();
    rand.set_state(state);
 
    return random_unsigned;
}

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎