Skip to content

Commit

Permalink
Move FastRNG to ax ns and more improvements (#2057)
Browse files Browse the repository at this point in the history
  • Loading branch information
DelinWorks authored Jul 28, 2024
1 parent 4c80b80 commit 4de7e28
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 75 deletions.
73 changes: 41 additions & 32 deletions core/math/FastRNG.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/****************************************************************************
Copyright (c) 2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
https://axmol.dev/
Expand All @@ -28,6 +25,12 @@
#ifndef __FAST_RNG_H__
#define __FAST_RNG_H__

#include "math/MathBase.h"
#include <type_traits>
#include <stdint.h>

NS_AX_MATH_BEGIN

/** A fast more effective seeded random number generator struct, uses xoshiro128**.
* It uses a simple algorithm to improve the speed of generating random numbers with a decent quality,
* Use this if you're planning to generate large amounts of random numbers in a single frame.
Expand All @@ -36,36 +39,56 @@
*/
struct FastRNG
{
private:
uint32_t s[4];

// SplitMix64 implementation, doesn't modify any state for this instance
// but it is used to seed xoshiro128** state
uint64_t nextSeed(uint64_t& state)
static inline uint64_t nextSeed(uint64_t& state)
{
uint64_t z = (state += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}

// returns a copy of x rotated k bits to the left
static inline uint32_t rotL(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }

// generates a random integer from 0 to max exclusive that is uniformly distributed using fastrange algorithm
uint32_t nextMax(uint32_t max)
{
uint64_t multiresult = static_cast<uint64_t>(next()) * max;
uint32_t leftover = static_cast<uint32_t>(multiresult);
if (leftover < max)
{
uint32_t threshold = (0 - max) % max;
while (leftover < threshold)
{
multiresult = static_cast<uint64_t>(next()) * max;
leftover = static_cast<uint32_t>(multiresult);
}
}
return multiresult >> 32;
}

public:
FastRNG() { seed(static_cast<uint64_t>(rand()) << 32 | rand()); }
FastRNG(uint64_t _seed) { seed(_seed); }

// there is no need to seed this instance of FastRNG
// because it has already been seeded with rand() by constructor
// because it's already been seeded with rand() in constructor
// you can override the seed by giving your own 64-bit seed
void seed(uint64_t seed)
{
uint64_t state = seed;
uint64_t states[2];
memset(states, 0, 16);
states[0] = nextSeed(state);
states[1] = nextSeed(state);
states[0] = FastRNG::nextSeed(state);
states[1] = FastRNG::nextSeed(state);
memcpy(s, states, 16);
}

// returns a copy of x rotated k bits to the left
static inline uint32_t rotL(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }

// steps once into the state, returns a random from 0 to UINT32_MAX
uint32_t next()
{
Expand All @@ -89,11 +112,12 @@ struct FastRNG
template <typename T>
T nextReal()
{
if (std::is_same<T, float>::value)
if constexpr (std::is_same<T, float>::value)
return static_cast<T>(next() >> 8) * 0x1.0p-24f;
else if (std::is_same<T, double>::value)
else if constexpr (std::is_same<T, double>::value)
return static_cast<T>((static_cast<uint64_t>(next()) << 32 | next()) >> 11) * 0x1.0p-53;
return 0; // possibly assert?
else
AXASSERT(false, "datatype not implemented.");
}

// generates a random real that ranges from min to max
Expand All @@ -110,23 +134,6 @@ struct FastRNG
return min + static_cast<T>(nextMax(static_cast<uint32_t>(max - min)));
}

// generates a random integer from 0 to max exclusive that is uniformly distributed using fastrange algorithm
uint32_t nextMax(uint32_t max)
{
uint64_t multiresult = static_cast<uint64_t>(next()) * max;
uint32_t leftover = static_cast<uint32_t>(multiresult);
if (leftover < max)
{
uint32_t threshold = (0 - max) % max;
while (leftover < threshold)
{
multiresult = static_cast<uint64_t>(next()) * max;
leftover = static_cast<uint32_t>(multiresult);
}
}
return multiresult >> 32;
}

// wrapper for nextInt<int32_t>(min, max)
int32_t range(int32_t min, int32_t max) { return nextInt<int32_t>(min, max); }
// wrapper for nextInt<int32_t>(0, max)
Expand All @@ -150,9 +157,11 @@ struct FastRNG
// wrapper for nextReal<float>()
float float01() { return nextReal<float>(); }
// wrapper for nextReal<double>()
float double01() { return nextReal<double>(); }
double double01() { return nextReal<double>(); }
// wrapper for next() & 1, true or false based on LSB
float bool01() { return next() & 1; }
bool bool01() { return static_cast<bool>(next() & 1); }
};

NS_AX_MATH_END

#endif // __FAST_RNG_H__
58 changes: 15 additions & 43 deletions tests/unit-tests/Source/core/math/FastRNGTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,15 @@


TEST_SUITE("math/FastRNG") {
TEST_CASE("nextSeed")
{
auto rng = FastRNG();

uint64_t s = 0x1234'5678'9ABC'EFFF;
CHECK_EQ(2299331620237437860u, rng.nextSeed(s));
CHECK_EQ(8718988738428180276u, rng.nextSeed(s));
}

TEST_CASE("rotL")
{
auto rng = FastRNG();

uint32_t s = 0x1357'9BDF;
CHECK_EQ(2939665958, rng.rotL(s, 9));
CHECK_EQ(4084982378, rng.rotL(s, 13));
}

TEST_CASE("next") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(1695105466, rng.next());
CHECK_EQ(1423115009, rng.next());
CHECK_EQ(634581793, rng.next());

auto rng2 = FastRNG();
auto rng2 = ax::FastRNG();
rng2.seed(2);

CHECK_EQ(1086064458, rng2.next());
Expand All @@ -65,7 +47,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("nextInt")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(12);

CHECK_EQ(30, rng.nextInt<int8_t>(INT8_MIN, INT8_MAX));
Expand All @@ -80,7 +62,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("nextReal")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(14);

CHECK_EQ(doctest::Approx(0.927014), rng.nextReal<float>());
Expand All @@ -92,18 +74,8 @@ TEST_SUITE("math/FastRNG") {
CHECK_EQ(doctest::Approx(9.94378e+307), rng.nextReal<double>(DBL_MIN, DBL_MAX));
}

TEST_CASE("nextMax")
{
auto rng = FastRNG();
rng.seed(16);

CHECK_EQ(1381652921, rng.nextMax(UINT32_MAX));
CHECK_EQ(46435, rng.nextMax(UINT16_MAX));
CHECK_EQ(84, rng.nextMax(UINT8_MAX));
}

TEST_CASE("range") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(12345);

CHECK_EQ(0, rng.range(0, 1));
Expand All @@ -119,7 +91,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("max")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0, rng.max(1));
Expand All @@ -129,7 +101,7 @@ TEST_SUITE("math/FastRNG") {


TEST_CASE("rangeu") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0u, rng.rangeu(0u, 1u));
Expand All @@ -141,7 +113,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("maxu")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0u, rng.maxu(1));
Expand All @@ -150,7 +122,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("rangef") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(-0.210655), rng.rangef(-1.0f, 1.0f));
Expand All @@ -163,7 +135,7 @@ TEST_SUITE("math/FastRNG") {


TEST_CASE("maxf") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.maxf(1.0f));
Expand All @@ -175,7 +147,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("ranged")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(-0.210655), rng.ranged(-1.0, 1.0));
Expand All @@ -185,7 +157,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("maxd")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.maxd(1.0f));
Expand All @@ -196,7 +168,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("float01") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.float01());
Expand All @@ -207,7 +179,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("double01")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.double01());
Expand All @@ -217,7 +189,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("bool01") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

auto t = 0;
Expand Down

0 comments on commit 4de7e28

Please sign in to comment.