不久前(预C++11),我写了一个小的随机数生成器库,作为大学作业的一部分。这些代码已经存储在我的硬盘上很长一段时间了,今天我把它挖出来,并决定把它放在这里进行审查。现在它已经过时了,因为C++11添加了一个非常广泛的随机库,但是我仍然希望收到关于如何改进这段代码的建议。
它遵循的是标准随机库的类似架构,并且应该易于扩展,尽管我只提供了两个引擎实现:
#pragma once
// ======================================================
// LCGEngine:
// ======================================================
// Implements a Linear Congruential Generator pseudo-random engine.
class LCGEngine {
public:
// Maximum value returned by NextValue().
static const int ValueMax = INT_MAX;
// Constructors:
LCGEngine();
LCGEngine(const unsigned int seed);
// Reset (seed) the pseudo-random generator engine.
void Seed(const unsigned int seed);
// Get the next value in the pseudo-random sequence.
int NextValue();
private:
// Random accumulators (LCG):
unsigned int base0;
unsigned int base1;
unsigned int base2;
};
// ======================================================
// XORShiftEngine:
// ======================================================
// Implements a XOR-Shift pseudo-random engine.
class XORShiftEngine {
public:
// Maximum value returned by NextValue().
static const int ValueMax = INT_MAX;
// Constructors:
XORShiftEngine();
XORShiftEngine(const unsigned int seed);
// Reset (seed) the pseudo-random generator engine.
void Seed(const unsigned int seed);
// Get the next value in the pseudo-random sequence.
int NextValue();
private:
unsigned int x, y, z, w;
};
// ======================================================
// Random:
// ======================================================
//
// Pseudo-random number generator class with several distribution functions.
//
// The Random class is a template that accepts any compliant
// random generator engine. This class implements the most common
// distribution functions for the engines.
//
template<class ENGINE>
class Random {
public:
// Type of the underlaying random generator engine.
typedef ENGINE EngineType;
// Max integer value that can be generated.
enum { ValueMax = ENGINE::ValueMax };
// Construct with seed based on current time.
Random();
// Construct with user defined seed.
Random(const unsigned int seed);
// Construct with a preexistent random generator engine.
Random(const ENGINE & eng);
// Copy state from another generator.
Random(const Random & other);
// Reset (seed) the pseudo-random generator.
void Seed(const unsigned int seed);
// Pseudo-random boolean. 'true' or 'false'.
bool NextBoolean();
// Pseudo-random floating-point number in range [0, 1].
float NextFloat();
// Pseudo-random floating-point number in range [lowerBound, upperBound].
float NextFloat(const float lowerBound, const float upperBound);
// Pseudo-random floating-point number in range [-1, 1].
float NextSymmetric();
// Pseudo-random integer in range [0, ValueMax].
int NextInteger();
// Pseudo-random integer in range [lowerBound, upperBound].
int NextInteger(const int lowerBound, const int upperBound);
// Pseudo-random number with uniform distribution.
float NextUniform(const float lowerBound, const float upperBound);
// Pseudo-random number with exponential distribution.
float NextExponential(const float average);
// Pseudo-random number with normal (Gaussian) distribution.
float NextGaussian(const float average, const float stdDeviation);
// Access the underlaying random generator engine.
ENGINE & GetRandomEngine();
// Access the underlaying random generator engine (const overload).
const ENGINE & GetRandomEngine() const;
private:
// The pseudo-random generator engine.
ENGINE engine;
// Helper variables used by the normal distribution calculation.
float leftover;
bool useLast;
};
// ======================================================
// Typedefs:
// ======================================================
// Linear Congruential Generator:
// http://en.wikipedia.org/wiki/Linear_congruential_generator
typedef Random<LCGEngine> LCGRandom;
// XOR-shift:
// http://en.wikipedia.org/wiki/Xorshift
typedef Random<XORShiftEngine> XORShiftRandom;
// ======================================================
// Helpers / globals:
// ======================================================
// Get a hash value based on the current time. Useful for seeding a pseudo-random generator.
unsigned int GenTimeSeed(const unsigned int seed);
#include "Engine/Core/Math/Random.inl"#include <cmath>
template<class ENGINE>
inline Random<ENGINE>::Random()
: engine()
, leftover(0.0f)
, useLast(false)
{
// Init with defaults.
}
template<class ENGINE>
inline Random<ENGINE>::Random(const unsigned int seed)
: engine(seed)
, leftover(0.0f)
, useLast(false)
{
// Init with user provided seed.
}
template<class ENGINE>
inline Random<ENGINE>::Random(const ENGINE & eng)
: engine(eng)
, leftover(0.0f)
, useLast(false)
{
// Init with engine.
}
template<class ENGINE>
inline Random<ENGINE>::Random(const Random<ENGINE> & other)
: engine(other.engine)
, leftover(other.leftover)
, useLast(other.useLast)
{
// Init from copy.
}
template<class ENGINE>
inline void Random<ENGINE>::Seed(const unsigned int seed)
{
engine.Seed(seed);
}
template<class ENGINE>
inline bool Random<ENGINE>::NextBoolean()
{
return ((engine.NextValue() & 1) == 0);
}
template<class ENGINE>
inline float Random<ENGINE>::NextFloat()
{
return (engine.NextValue() * (1.0f / ENGINE::ValueMax));
}
template<class ENGINE>
inline float Random<ENGINE>::NextFloat(const float lowerBound, const float upperBound)
{
const float f = (static_cast<float>(engine.NextValue()) + 0.5f) / ENGINE::ValueMax;
return (lowerBound + (upperBound - lowerBound) * f);
}
template<class ENGINE>
inline float Random<ENGINE>::NextSymmetric()
{
return (2.0f * NextFloat() - 1.0f);
}
template<class ENGINE>
inline int Random<ENGINE>::NextInteger()
{
return (engine.NextValue());
}
template<class ENGINE>
inline int Random<ENGINE>::NextInteger(const int lowerBound, const int upperBound)
{
if (lowerBound == upperBound)
{
return (lowerBound); // Avoid a division by zero.
}
return ((engine.NextValue() % (upperBound - lowerBound)) + lowerBound);
}
template<class ENGINE>
inline float Random<ENGINE>::NextUniform(const float lowerBound, const float upperBound)
{
return (lowerBound + (NextFloat() * (upperBound - lowerBound)));
}
template<class ENGINE>
inline float Random<ENGINE>::NextExponential(const float average)
{
return (-average * std::log(1.0f - NextFloat()));
}
template<class ENGINE>
inline float Random<ENGINE>::NextGaussian(const float average, const float stdDeviation)
{
// See http://www.taygeta.com/random/gaussian.html
// for a full explanation.
float x1, x2, y1, y2, w;
if (useLast) // Use value from previous call?
{
y1 = leftover;
useLast = false;
}
else
{
do
{
x1 = (2.0f * NextFloat()) - 1.0f;
x2 = (2.0f * NextFloat()) - 1.0f;
w = (x1 * x1) + (x2 * x2);
}
while (w >= 1.0f);
w = std::sqrt((-2.0f * std::log(w)) / w);
y1 = (x1 * w);
y2 = (x2 * w);
leftover = y2;
useLast = true;
}
return (average + y1 * stdDeviation);
}
template<class ENGINE>
inline ENGINE & Random<ENGINE>::GetRandomEngine()
{
return (engine);
}
template<class ENGINE>
inline const ENGINE & Random<ENGINE>::GetRandomEngine() const
{
return (engine);
}#include "Core/Utilities.hpp"
#include "Core/Math/Random.hpp"
#include <ctime>
// ======================================================
// LCGEngine:
// ======================================================
LCGEngine::LCGEngine()
// Arbitrary constants:
: base0(42)
, base1(666)
, base2(1337)
{
}
LCGEngine::LCGEngine(const unsigned int seed)
{
Seed(seed);
}
void LCGEngine::Seed(const unsigned int seed)
{
base0 = seed;
base1 = seed;
base2 = seed;
}
int LCGEngine::NextValue()
{
// Triple LCG:
base0 = (1664525 * base0-1 + 1013904223) % ValueMax;
switch (base0 & 1)
{
case 0 :
base1 = (214013 * base1-1 + 2531011) % ValueMax;
return (base1);
case 1 :
base2 = (1103515245 * base2-1 + 12345) % ValueMax;
return (base2);
default :
return (base0); // This never happens, it is here just to please the compiler.
} // switch (base0 & 1)
}
// ======================================================
// XORShiftEngine:
// ======================================================
XORShiftEngine::XORShiftEngine()
// Constants presented in this paper: http://www.jstatsoft.org/v08/i14/paper
: x(123456789U)
, y(362436069U)
, z(521288629U)
, w(88675123U)
{
}
XORShiftEngine::XORShiftEngine(const unsigned int seed)
{
Seed(seed);
}
void XORShiftEngine::Seed(const unsigned int seed)
{
x = (seed ^ 123456789U);
y = (seed ^ 362436069U);
z = (seed ^ 521288629U);
w = (seed ^ 88675123U);
}
int XORShiftEngine::NextValue()
{
// XOR-Shift-128 RNG:
unsigned int t = x ^ (x << 11);
x = y;
y = z;
z = w;
w = w ^ (w >> 19) ^ t ^ (t >> 8);
return (w);
}
// ======================================================
// GenTimeSeed():
// ======================================================
unsigned int GenTimeSeed(const unsigned int seed)
{
// Zero is an OK default value for 'seed'
const time_t now = std::time(nullptr);
const unsigned char * p = reinterpret_cast<const unsigned char *>(&now);
unsigned int s = seed;
for (size_t i = 0; i < sizeof(now); ++i)
{
s = s * (0xff + 2U) + p[i];
}
return (s);
}我从来没有彻底地使用过它,所以我不知道它在更苛刻的情况下会如何坚持下去。这不是为了密码安全而设计的,它是用于实时应用程序,如游戏、高度图发生器、噪音函数等等。
我认为NextInteger(const int lowerBound, const int upperBound)可能毫无价值,因为它使用%,据我所知,它完全消除了分布。
另外,LCGEngine::NextValue()使用了我编的“三重随机”。不确定这是否有什么好处..。
其他建议也很受欢迎。如果您想建议使用C++11特性,请放心,因为我有一个兼容的编译器。
发布于 2014-09-19 07:57:39
explicit,您真的不希望一个unsigned隐式转换为Random。NextBoolean()的一个更好的实现(特别是对于坏引擎)是检查该值是否大于/小于中位数。在所有比特上均匀分布比在值范围上均匀分布更难实现。NextInteger(int lowerBound, int upperBound)的方法并不会完全消除发行版。如果INT_MAX与0模(upperBound - lowerBound)一致,或者换句话说是2的幂,它甚至不会损害它。但是线性同余发生器还有一个额外的问题,因为它们自己的均匀分布和它们在比特上的分布是非常糟糕的。要实现一个好的实现,您必须从生成器中获取最多两个数字,并且您必须做一些旋转操作。NextFloat()。在上面的注释中,您可以声明它的返回值在0,1中,如果您使用了无符号类型,那么它的返回值将是正确的。有符号整数,它在-1,1中。x * (1 / y)是编写x / y的一种模糊方式。NextSymmetric()是错误的,因为NextFloat()是错误的。NextFloat(float lowerBound, float upperBound),则结果值属于类别(对应于NextFloat()在0至1之间返回的初始值),因此该函数不能返回此范围内所有可能的数字。但目前,除了一些平台依赖于浮点数的位旋转之外,我还不知道更好的实现方式。NextFloat和NextUniform之间的区别。有吗?如果不是,为什么有两个不同的实现而不是NextUniform调用NextFloat?reinterpret_cast中使用未定义的GenTimeSeed。inline,因为现代编译器比程序员更清楚什么对内联有好处,什么不是(无论如何,他们都会忽略它)。对不起我的英语不好。希望这是足够清楚的理解。
https://codereview.stackexchange.com/questions/63317
复制相似问题