diff --git a/core/math/FastRNG.h b/core/math/FastRNG.h index 4aa8afcf808..8733b52ff5c 100644 --- a/core/math/FastRNG.h +++ b/core/math/FastRNG.h @@ -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/ @@ -28,6 +25,12 @@ #ifndef __FAST_RNG_H__ #define __FAST_RNG_H__ +#include "math/MathBase.h" +#include +#include + +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. @@ -36,11 +39,12 @@ */ 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; @@ -48,24 +52,43 @@ struct FastRNG 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(next()) * max; + uint32_t leftover = static_cast(multiresult); + if (leftover < max) + { + uint32_t threshold = (0 - max) % max; + while (leftover < threshold) + { + multiresult = static_cast(next()) * max; + leftover = static_cast(multiresult); + } + } + return multiresult >> 32; + } + +public: FastRNG() { seed(static_cast(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() { @@ -89,11 +112,12 @@ struct FastRNG template T nextReal() { - if (std::is_same::value) + if constexpr (std::is_same::value) return static_cast(next() >> 8) * 0x1.0p-24f; - else if (std::is_same::value) + else if constexpr (std::is_same::value) return static_cast((static_cast(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 @@ -110,23 +134,6 @@ struct FastRNG return min + static_cast(nextMax(static_cast(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(next()) * max; - uint32_t leftover = static_cast(multiresult); - if (leftover < max) - { - uint32_t threshold = (0 - max) % max; - while (leftover < threshold) - { - multiresult = static_cast(next()) * max; - leftover = static_cast(multiresult); - } - } - return multiresult >> 32; - } - // wrapper for nextInt(min, max) int32_t range(int32_t min, int32_t max) { return nextInt(min, max); } // wrapper for nextInt(0, max) @@ -150,9 +157,11 @@ struct FastRNG // wrapper for nextReal() float float01() { return nextReal(); } // wrapper for nextReal() - float double01() { return nextReal(); } + double double01() { return nextReal(); } // wrapper for next() & 1, true or false based on LSB - float bool01() { return next() & 1; } + bool bool01() { return static_cast(next() & 1); } }; +NS_AX_MATH_END + #endif // __FAST_RNG_H__ diff --git a/tests/unit-tests/Source/core/math/FastRNGTests.cpp b/tests/unit-tests/Source/core/math/FastRNGTests.cpp index 1b3f6bded26..36008408cb9 100644 --- a/tests/unit-tests/Source/core/math/FastRNGTests.cpp +++ b/tests/unit-tests/Source/core/math/FastRNGTests.cpp @@ -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()); @@ -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_MIN, INT8_MAX)); @@ -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()); @@ -92,18 +74,8 @@ TEST_SUITE("math/FastRNG") { CHECK_EQ(doctest::Approx(9.94378e+307), rng.nextReal(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)); @@ -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)); @@ -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)); @@ -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)); @@ -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)); @@ -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)); @@ -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)); @@ -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)); @@ -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()); @@ -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()); @@ -217,7 +189,7 @@ TEST_SUITE("math/FastRNG") { } TEST_CASE("bool01") { - auto rng = FastRNG(); + auto rng = ax::FastRNG(); rng.seed(1); auto t = 0;