首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >小型预C++11伪随机库

小型预C++11伪随机库
EN

Code Review用户
提问于 2014-09-19 03:56:08
回答 1查看 393关注 0票数 4

不久前(预C++11),我写了一个小的随机数生成器库,作为大学作业的一部分。这些代码已经存储在我的硬盘上很长一段时间了,今天我把它挖出来,并决定把它放在这里进行审查。现在它已经过时了,因为C++11添加了一个非常广泛的随机库,但是我仍然希望收到关于如何改进这段代码的建议。

它遵循的是标准随机库的类似架构,并且应该易于扩展,尽管我只提供了两个引擎实现:

Random.hpp

代码语言:javascript
复制
#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"

Random.inl

代码语言:javascript
复制
#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);
}

Random.cpp

代码语言:javascript
复制
#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特性,请放心,因为我有一个兼容的编译器。

EN

回答 1

Code Review用户

回答已采纳

发布于 2014-09-19 07:57:39

  1. 在没有任何效果的参数上使用许多不必要的顶级cv限定符。这不仅在我看来很奇怪,而且也安静了一些不必要的打字工作。
  2. 您自己显式地实现了复制构造函数,而编译器生成的编译器将具有完全相同的行为。这也意味着,如果要扩展类,就必须更新一个额外的方法。
  3. 我建议您让所有的非复制构造函数explicit,您真的不希望一个unsigned隐式转换为Random
  4. NextBoolean()的一个更好的实现(特别是对于坏引擎)是检查该值是否大于/小于中位数。在所有比特上均匀分布比在值范围上均匀分布更难实现。
  5. 通常,实现NextInteger(int lowerBound, int upperBound)的方法并不会完全消除发行版。如果INT_MAX与0模(upperBound - lowerBound)一致,或者换句话说是2的幂,它甚至不会损害它。但是线性同余发生器还有一个额外的问题,因为它们自己的均匀分布和它们在比特上的分布是非常糟糕的。要实现一个好的实现,您必须从生成器中获取最多两个数字,并且您必须做一些旋转操作。
  6. 两个引擎都返回有符号整数。在我看来,这有点奇怪,因为无符号整数更容易使用,特别是当涉及到位移位时。正因为如此,您错误地实现了NextFloat()。在上面的注释中,您可以声明它的返回值在0,1中,如果您使用了无符号类型,那么它的返回值将是正确的。有符号整数,它在-1,1中。
  7. x * (1 / y)是编写x / y的一种模糊方式。
  8. NextSymmetric()是错误的,因为NextFloat()是错误的。
  9. 如果调用具有较大范围的NextFloat(float lowerBound, float upperBound),则结果值属于类别(对应于NextFloat()在0至1之间返回的初始值),因此该函数不能返回此范围内所有可能的数字。但目前,除了一些平台依赖于浮点数的位旋转之外,我还不知道更好的实现方式。
  10. 我不太明白NextFloatNextUniform之间的区别。有吗?如果不是,为什么有两个不同的实现而不是NextUniform调用NextFloat
  11. 您可以通过使用位掩码和位移位来避免在reinterpret_cast中使用未定义的GenTimeSeed
  12. 您不必编写inline,因为现代编译器比程序员更清楚什么对内联有好处,什么不是(无论如何,他们都会忽略它)。
  13. 在我看来,显式调用成员默认构造函数(因为这也是隐式的)看起来很奇怪,但这只是一种看法。

对不起我的英语不好。希望这是足够清楚的理解。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/63317

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档