// bdlb_pcgrandomgenerator.h -*-C++-*- #ifndef INCLUDED_BDLB_PCGRANDOMGENERATOR #define INCLUDED_BDLB_PCGRANDOMGENERATOR #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a class to generate random numbers using the PCG algorithm. // //@CLASSES: // bdlb::PcgRandomGenerator: PCG-based random number generator (RNG) // //@SEE_ALSO: bdlb_random // //@DESCRIPTION: This component provides a single mechanism class, // 'bdlb::PcgRandomGenerator', that is used to generate random numbers // employing the PCG algorithm, a high-performance, high-quality RNG. PCG // stands for "permuted congruential generator" (see http://www.pcg-random.org // for details). The PCG technique employs the concepts of permutation // functions on tuples and a base linear congruential generator. The PCG // algorithm is seeded with two values: its initial state and a "stream // selector." The stream selector is intended to address a potential hazard of // multiple instances of a random number generator having unintended // correlation between their outputs. For example, if we allow them to have // the same internal state (e.g., mistakenly seeding them with the current time // in seconds), they will output the exact same sequence of numbers. Employing // a stream selector enables the same initial state to generate unique // sequences. Free operators '==' and '!=' provide the operational definition // of value. Refer to O'Neill (2014) at // https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf for details. // // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Simulating a Pair of Dice /// - - - - - - - - - - - - - - - - - - // This example shows how one might use 'bdlb::PcgRandomGenerator' to create // and use a class to simulate the roll of a single die in a game, such as // craps, that uses dice. // // First, we define the 'Die' class itself: //.. // // ========= // // class Die // // ========= // // class Die { // // // DATA // bdlb::PcgRandomGenerator d_pcg; // // used to generate next role of this die // // public: // // CREATORS // Die(bsl::uint64_t initialState, bsl::uint64_t streamSelector); // // Create an object used to simulate a single die, using the // // specified 'initialState' and 'streamSelector'. // // // MANIPULATORS // int roll(); // // Return the next pseudo-random value in the range '[1 .. 6]', // // based on the sequence of values established by the // // 'initialState' and 'streamSelector' values supplied at // // construction. // }; // // // --------- // // class Die // // --------- // // // CREATORS // inline // Die::Die(bsl::uint64_t initialState, bsl::uint64_t streamSelector) // : d_pcg(initialState, streamSelector) // { // } // // // MANIPULATORS // int Die::roll() // { // int result; // // do { // result = d_pcg.generate() & 7; // } while (result > 5); // // return result + 1; // } //.. // Now, we can use our 'Die' class to get the random numbers needed to // simulate a game of craps. Note that the game of craps requires two dice. // // We can instantiate a single 'Die' and role it twice: //.. // void rollOneDieTwice() // { // Die a(123, 456); // // int d1 = a.roll(); // int d2 = a.roll(); // // cout << "d1 = " << d1 << ", d2 = " << d2 << endl; // d1 = 3, d2 = 5 // } //.. // Alternatively, we could create two instances of 'Die', with separate initial // states/sequences, and role each one once: //.. // void rollTwoDice() // { // Die a(123, 123); // Die b(456, 456); // // int d1 = a.roll(); // int d2 = b.roll(); // // cout << "d1 = " << d1 << ", d2 = " << d2 << endl; // d1 = 3, d2 = 1 // } //.. // Note that the specification of separate seeds is important to produce a // proper distribution for our game. If we had shared the seed value each die // would always produce the same sequence of values as the other. //.. // void shareStateAndSequence() // { // Die a(123, 456); // BAD IDEA // Die b(123, 456); // BAD IDEA // // int d1 = a.roll(); // int d2 = b.roll(); // assert(d2 == d1); // } //.. ///Example 2: Using a Stream Selector to Guarantee Uncorrelated Sequences /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // This example illustrates how a stream selector can be used to guarantee // that two distinct instances of 'PcgRandomGenerator' produce uncorrelated // sequences. // // First, we use 'bdlb::RandomDevice' to choose the initial states for the // generators using a source of true randomness: //.. // uint64_t state1; // int rc = bdlb::RandomDevice::getRandomBytes( // reinterpret_cast<unsigned char *>(&state1), sizeof(state1)); // (void)rc; // error handling omitted // // uint64_t state2; // int rc = bdlb::RandomDevice::getRandomBytes( // reinterpret_cast<unsigned char *>(&state2), sizeof(state2)); // (void)rc; // error handling omitted //.. // Then we select two distinct stream selectors for the generators, which, due // to the PCG algorithmic properties, will guarantee that the sequences will be // uncorrelated even if the initial state is exactly the same: //.. // const uint64_t streamSelector1 = 1; // const uint64_t streamSelector2 = 2; //.. // Finally, we initialize the generators with their respective initial states // and stream selectors and check that they produce distinct sequences of // random numbers. The check is guaranteed to pass even in the rare, but // possible case of 'state1 == state2': //.. // bdlb::PcgRandomGenerator rng1(state1, streamSelector1); // bdlb::PcgRandomGenerator rng2(state2, streamSelector2); // // const int NUM_VALUES = 1000; // bsl::vector<bsl::uint32_t> sequence1(NUM_VALUES); // bsl::vector<bsl::uint32_t> sequence2(NUM_VALUES); // // for (int i = 0; i < NUM_VALUES; ++i) { // sequence1[i] = rng1.generate(); // sequence2[i] = rng2.generate(); // } // // assert(sequence1 != sequence2); //.. #include <bdlscm_version.h> #include <bsls_keyword.h> #include <bsl_cstdint.h> namespace BloombergLP { namespace bdlb { // ======================== // class PcgRandomGenerator // ======================== class PcgRandomGenerator { // This class implements a random number generator (RNG) based on the PCG // algorithm. // DATA bsl::uint64_t d_state; // the RNG state bsl::uint64_t d_streamSelector; // selected sequence (stream) // FRIENDS friend bool operator==(const PcgRandomGenerator& lhs, const PcgRandomGenerator& rhs); public: // CREATORS PcgRandomGenerator(); PcgRandomGenerator(bsl::uint64_t initState, bsl::uint64_t streamSelector); // Create a 'PcgRandomGenerator' object and seed it with the optionally // specified 'initState' and 'streamSelector'. If 'initState' and // 'streamSelector' are not specified, 0 is used for both. The // highest-order bit of 'streamSelector' is ignored. Note that // invoking different instances created with the identical 'initState' // and 'streamSelector' will result in the same sequence of random // numbers from subsequent invocations of 'generate()'. //! PcgRandomGenerator(const PcgRandomGenerator& original) = default; // Create a 'PcgRandomGenerator' object having the same value as the // specified 'original' object. Note that this newly created object // will generate the same sequence of numbers as 'original'. // MANIPULATORS //! PcgRandomGenerator& operator=(const PcgRandomGenerator& rhs) = default; // Assign to this object the value of the specified 'rhs' object, and // return a non-'const' reference to this object. Note that the object // after assignment will generate the same sequence of numbers as // 'rhs'. bsl::uint32_t generate(); // Return the next random number in the sequence generated by this // object. void seed(bsl::uint64_t initState, bsl::uint64_t streamSelector); // Seed this generator with the specified new 'initState' and // 'streamSelector'. Note that the sequence of random numbers produced // from subsequent invocations of 'generate()' will be the same as that // produced by an object created by // 'PcgRandomGenerator(initState, streamSelector)'. }; // FREE OPERATORS bool operator==(const PcgRandomGenerator& lhs, const PcgRandomGenerator& rhs); // Return 'true' if the specified 'lhs' and 'rhs' objects have the same // value, and 'false' otherwise. Two 'PcgRandomGenerator' objects have the // same value if they would produce the same sequence of random numbers // from subsequent invocations of 'generate()'. bool operator!=(const PcgRandomGenerator& lhs, const PcgRandomGenerator& rhs); // Return 'true' if the specified 'lhs' and 'rhs' objects do not have the // same value, and 'false' otherwise. Two 'PcgRandomGenerator' objects do // not have the same value if they would not produce the same sequence of // random numbers from subsequent invocations of 'generate()'. // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ------------------------ // class PcgRandomGenerator // ------------------------ // CREATORS inline PcgRandomGenerator::PcgRandomGenerator() { seed(0, 0); } inline PcgRandomGenerator::PcgRandomGenerator(bsl::uint64_t initState, bsl::uint64_t streamSelector) { seed(initState, streamSelector); } // MANIPULATORS inline bsl::uint32_t PcgRandomGenerator::generate() { static BSLS_KEYWORD_CONSTEXPR_MEMBER bsl::uint64_t k_MULTIPLIER = 6364136223846793005ULL; bsl::uint64_t oldstate = d_state; // Advance the internal state d_state = oldstate * k_MULTIPLIER + d_streamSelector; // Perform the output function bsl::uint32_t xorshifted = static_cast<bsl::uint32_t>(((oldstate >> 18u) ^ oldstate) >> 27u); bsl::uint32_t rot = static_cast<bsl::uint32_t>(oldstate >> 59u); return (xorshifted >> rot) | (xorshifted << ((0u - rot) & 31u)); } inline void PcgRandomGenerator::seed(bsl::uint64_t initState, bsl::uint64_t streamSelector) { d_streamSelector = (streamSelector << 1u) | 1u; d_state = 0U; generate(); d_state += initState; generate(); } } // close package namespace // FREE OPERATORS inline bool bdlb::operator==(const PcgRandomGenerator& lhs, const PcgRandomGenerator& rhs) { return lhs.d_state == rhs.d_state && lhs.d_streamSelector == rhs.d_streamSelector; } inline bool bdlb::operator!=(const PcgRandomGenerator& lhs, const PcgRandomGenerator& rhs) { return !(lhs == rhs); } } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2020 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------