-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Real-time-safe random number generator.
Xorshift random number generator is a constant-time pseudorandom generator. We also create an "uniform" distribution that is also constant time. This means it is not 100% uniform as it is not allowed to discard a number and generate a different number if it gets 1.0. The non-uniformity is very small tho so it should work for non-cryptographic purposes (which you probably shouldn't be doing in real-time anyway).
- Loading branch information
Showing
6 changed files
with
161 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
add_executable(random_example | ||
main.cc | ||
) | ||
|
||
target_link_libraries(random_example | ||
PRIVATE | ||
cactus_rt | ||
) | ||
|
||
setup_cactus_rt_target_options(random_example) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#include <cactus_rt/experimental/random.h> | ||
|
||
#include <array> | ||
#include <iomanip> | ||
#include <iostream> | ||
#include <random> | ||
|
||
template <size_t N> | ||
class Histogram { | ||
std::array<size_t, N> hist_; | ||
|
||
public: | ||
Histogram() : hist_({0}) {} | ||
|
||
void Record(float value) { | ||
const auto i = static_cast<size_t>(value * N); | ||
hist_.at(i)++; | ||
} | ||
|
||
void Display() { | ||
constexpr float width = 1.0F / static_cast<float>(N); | ||
float current_bucket = 0.0F; | ||
for (size_t i = 0; i < N; i++) { | ||
std::cout << std::setprecision(4) << current_bucket << ": " << hist_[i] << "\n"; | ||
current_bucket += width; | ||
} | ||
} | ||
}; | ||
|
||
int main() { | ||
uint64_t seed = std::random_device{}(); | ||
|
||
Histogram<20> hist; | ||
|
||
cactus_rt::experimental::Xorshift64Rand rng(seed); | ||
|
||
for (int i = 0; i < 1'000'000; i++) { | ||
float num = cactus_rt::experimental::RandomRealNumber(rng); | ||
if (num >= 1.0F || num < 0.0F) { | ||
std::cerr << "ERROR: seed = " << seed << " i = " << i << " num = " << num << " is out of range \n"; | ||
return 1; | ||
} | ||
hist.Record(num); | ||
} | ||
hist.Display(); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
#ifndef CACTUS_RT_EXPERIMENTAL_RAND_H_ | ||
#define CACTUS_RT_EXPERIMENTAL_RAND_H_ | ||
|
||
#include <cstdint> | ||
#include <limits> | ||
|
||
/** | ||
* C++'s random number engines and random number distributions are amortized | ||
* O(1) which as we know is greater than O(1) (theoretically O(inf)? But | ||
* statistically very unlikely?). See Real-time Programming with the C++ | ||
* Standard Library - Timur Doumler - CppCon 2021[1]. | ||
* | ||
* Thus we implement the Xorshift algorithm[2][3] to generate random numbers. | ||
* This is not a cryptographically safe random number generator. Notably the | ||
* uniform distribution implemented here do not guarantee perfect uniformity as | ||
* it never discard numbers to generate another number (to ensure we can | ||
* generate in O(1) and not amortized O(1)). | ||
* | ||
* [1]: https://youtu.be/Tof5pRedskI?t=2514 | ||
* [2]: https://en.wikipedia.org/wiki/Xorshift | ||
* [3]: https://doi.org/10.18637/jss.v008.i14 | ||
*/ | ||
|
||
namespace cactus_rt::experimental { | ||
class Xorshift64Rand { | ||
uint64_t x_; | ||
|
||
public: | ||
using result_type = uint64_t; | ||
|
||
explicit Xorshift64Rand(result_type initial_state) : x_(initial_state) {} | ||
|
||
result_type operator()() { | ||
x_ ^= (x_ << 13); | ||
x_ ^= (x_ >> 7); | ||
x_ ^= (x_ << 17); | ||
return x_; | ||
}; | ||
|
||
static constexpr result_type min() { | ||
return 1; | ||
} | ||
|
||
static constexpr result_type max() { | ||
return std::numeric_limits<uint64_t>::max(); | ||
} | ||
}; | ||
|
||
/** | ||
* @brief Return a random number between [0, 1). Similar to | ||
* std::uniform_real_distribution but not an object as it has no state. This is | ||
* not a perfect uniform distribution and has some minor amount of bias, which | ||
* is OK for real-time usage. It will also repeat as it doesn't allow you | ||
* reseed. | ||
* | ||
* @tparam T The data type of the return result, default to float. | ||
* @tparam Generator The random engine, default to Xorshift64Rand which is real-time safe. | ||
* @param rng The RNG generator instance. | ||
* @return T A random number between [0, 1) | ||
*/ | ||
template <typename T = float, typename Generator = Xorshift64Rand> | ||
T RandomRealNumber(Generator& rng) { | ||
T v = static_cast<T>(rng() - Generator::min()) / static_cast<T>(Generator::max() - Generator::min()); | ||
if (v == static_cast<T>(1.0)) { | ||
// Random numbers are supposed to be [0, 1). This is a hack to make sure we never land on 1. | ||
return v - std::numeric_limits<T>::epsilon(); | ||
} | ||
|
||
return v; | ||
} | ||
|
||
} // namespace cactus_rt::experimental | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#include "cactus_rt/experimental/random.h" | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include <random> | ||
|
||
using cactus_rt::experimental::RandomRealNumber; | ||
using cactus_rt::experimental::Xorshift64Rand; | ||
|
||
TEST(RandomRealNumber, Generate) { | ||
const uint64_t seed = std::random_device{}(); | ||
Xorshift64Rand rng(seed); | ||
|
||
for (int i = 0; i < 1'000'000; i++) { | ||
const float current = RandomRealNumber(rng); | ||
if (current < 0.0F || current >= 1.0F) { | ||
ADD_FAILURE() << "number generated out of range: " << current << " (seed = " << seed << ", i = " << i << ")"; | ||
} | ||
} | ||
|
||
for (int i = 0; i < 1'000'000; i++) { | ||
const auto current = RandomRealNumber<double>(rng); | ||
if (current < 0.0 || current >= 1.0) { | ||
ADD_FAILURE() << "number generated out of range: " << current << " (seed = " << seed << ", i = " << i << ")"; | ||
} | ||
} | ||
} |