【问题标题】:Random but predictable number generator? [C++]随机但可预测的数字生成器? [C++]
【发布时间】:2012-02-13 23:10:51
【问题描述】:

嗯,我真的不知道如何搜索我正在寻找的东西。 Google 提供了大量结果,但没有一个符合我的标准。

所以我在这里问它: 是否有任何已知的代码可以创建一个可预测的、看起来随机的、基于“种子”(在我的例子中是 unix 时间戳)和指定范围之间的代码?

我希望能够在我正在编码的游戏的脚本中创建天气预报(但我需要可以移植的 C++ 代码,我认为这里的很多人都不熟悉“PAWN”[a.k.a.小]脚本语言?:))。 天气 ID 从 0 到 ~100 不等,包括一些不推荐使用的 ID(所以我的解决方案是制作一个包含有效天气 ID 的数组,这样我们就不必担心那些 BAD_ID,我们不要让函数太复杂)。

我可能会制定这样的公式,但过去我遇到的问题是天气变化太快(就像每一秒一样,虽然我在某处丢失了代码:/),现在我真的没有想法我将如何制作这样的公式。

任何建议也非常感谢!

【问题讨论】:

  • 看看Boost.Random
  • 如果您的天气变化太快,解决方案不是使用“更少”的随机发生器——而是减少选择不同模式的频率。如果 'Sun' 和 'Snowstorm' 的 id 彼此相邻也无济于事。
  • 还有其他 random() 函数不需要我移植这么多代码吗? (PHP、PAWN、C++
  • 很难理解你的意思。正如其他人所说,“随机”和“可预测”是相互排斥的。当然,PRNG 是完全可预测的,这可能就是您一直在使用的。如果 X-Zero 不那么频繁地选择随机数的建议不是您所需要的,那么您可能要询问的主题是“噪音”。使用 perlin 噪声等噪声函数,您可以构建一个在先前值范围内变化的函数,并且可以模拟很多自然的效果。

标签: c++ random formula weather forecasting


【解决方案1】:

查看random number generator used by VB6 的C 实现。它非常适合游戏,因为它生成相当可信的随机序列,但使用种子,并且相同的种子总是生成相同的序列。因此,在游戏数据文件中,您可以保存一组种子值,这些值将为您提供已知(但外观随机)的序列,您可以轻松重现这些序列。

这是一个返回范围内值的实现:

typedef int Int32;
typedef unsigned int UInt32;

class CRnd
{
    private:
        static const UInt32 INITIAL_VALUE = 0x50000;
        static const UInt32 INCREMENT = 0xC39EC3;
        static const UInt32 MULTIPLIER = 0x43FD43FD;

    private:
        UInt32 m_nRnd;

    public:
        CRnd () { m_nRnd = INITIAL_VALUE; };
        CRnd ( IN UInt32 nSeed ) { m_nRnd = nSeed; };
        virtual ~CRnd () {};

        Int32 Get ( IN Int32 nFrom, IN Int32 nTo )
        {
            if ( nTo < nFrom ) // nFrom should be less than nTo
            {
                Int32 nTmp = nTo;

                nTo = nFrom;
                nFrom = nTmp;
            }
            else if ( nTo == nFrom )
            {
                return ( nTo );
            }

            m_nRnd = ( m_nRnd * MULTIPLIER + INCREMENT ) & 0xFFFFFF;

            float fTmp = (float) m_nRnd / (float) 16777216.0;

            return ( (Int32) ( ( fTmp * ( nTo - nFrom + 1 ) ) + nFrom ) );
        };

        void SetSeed ( IN UInt32 nSeed ) { m_nRnd = nSeed; };
        UInt32 GetSeed () { return ( m_nRnd ); };
};

【讨论】:

  • 哇,谢谢,结果真的很好,看看:pastebin.com/WvgTSv7g代码:pastebin.com/D4KrXpgf
  • 不客气 - 希望它对你有用。我一直喜欢这种简单的算法,因为它具有可重复性。只需很少的输入,就可以生成巨大的恒星系统。
  • 需要注意的是,这是一个非常糟糕的随机数生成器(许多网站都对它的统计特性进行了令人作呕的解释)。此外,它使用了一个完全非标准的接口,这使得它很难与其他库一起使用。它不应该在新代码中使用。
  • @KonradRudolph 你介意提供你提到的那些网站的链接吗?
  • LCG 的一般问题在on Wikipediain numerous papers 中进行了解释。 Julienne Walker 解释了上述代码的其他问题。
【解决方案2】:

请先查看srandrand

C++11 还包含许多更高级的算法,但对于基本需求,以上两个就足够了。

要将数字保持在 0 到 n 的范围内,请使用 % 运算符。

【讨论】:

  • 不幸的是,srand() 和 rand() 在许多系统上往往很慢,或者随机性质量很差,这可能会以意想不到的方式出现,特别是如果您使用这个问题所暗示的有序输入。
  • rand 通常对于游戏来说已经足够了。如果您是统计学家,这当然不是很好——它往往是滚动、乘法和加法,或者在复杂性方面类似的东西。速度方面,我从未见过任何抱怨的理由;您在哪些平台上发现了问题?
  • 说默认 rand 通常对游戏来说足够好是一个虚假的概念。在 Linux 上,通常是这样。在 Windows 上,碰碰运气——他们有一些非常糟糕的 rand 实现。我遇到了一个多平台游戏的问题,它在一个平台上有奇怪的行为,但在另一个平台上却没有,因为 rand 过于简单化了。而在图形编程中,它变得更加夸张。
【解决方案3】:

显然,一个数字不能同时是“可预测的”和“随机的”——它们是直接矛盾的术语。

我假设你的意思是一个既确定性又是半随机的数字。

幸运的是,这就是伪随机数生成器 (PRNG) 产生的结果:当它们使用一致的种子运行时,它们会为您提供相同的输出。

因此,我建议您使用srandom 设置种子,然后使用random() % MAX_VALUE 获取介于0 和MAX_VALUE 之间的数字。如果你得到一个“坏值”,那就再去一次。重复 sans 重新播种任意数量的数字。

【讨论】:

    【解决方案4】:

    如果您需要一个缓慢变化的值,您可以使用噪声函数,例如Perlin Noise

    【讨论】:

      【解决方案5】:

      你真正想要的是一个散列函数。要限制范围,您可以使用一种常用技巧(最脏的是余数运算符)。

      具体来说,您希望将整数散列成整数。你可以拿起这样的功能here。我推荐标题为“罗伯特·詹金斯的 32 位整数散列函数”的文章——对我来说总是很有效。

      你最终会得到类似的东西:

      int time_index = 3;
      int weather_state = integer_hash_function(time_index) % (MAX_VALUE - MIN_VALUE + 1) + MIN_VALUE
      

      如果您想要更有趣的天气行为,您可以在时间值之间进行线性插值。您可以将 Perlin 噪声与此类插值噪声在不同频率和强度下的线性组合一起使用,以产生一些非常好的行为。 (我已经在多人 RPG 中做到了这一点,而且效果很好。)

      【讨论】:

        【解决方案6】:

        srandrand 的问题在于,只有它们的调用签名(而不是它们生成的值)是由 C 标准规定的。如果您需要可移植的确定性伪随机数,您应该自己实现它。这是一个用 C++ 编写的类,它基于 Numerical Recipes 中的类,并且是完全可移植的。如果您愿意,可以使用种子实例化随机数流。我对这个种子进行硬编码,而不是使用时间,以防我一次又一次地想要相同的伪随机序列。也可以使用RandomInteger(a,b)方法获取半开区间[a,b)上的整数。

        class RandomNumberStream
        {
        private:
          unsigned long long u,v,w;
        
        public:
          RandomNumberStream(int n=1);
          double RandomDouble();
          double RandomDouble(double a, double b);
          unsigned long long RandomInteger();
          unsigned long long RandomInteger(int a, int b);
        private:
          unsigned long long int64();
        } ;
        
        
        
        RandomNumberStream::RandomNumberStream(int n)
        {
          v = 4101842887655102017LL;
          w = 1;
        
          u = n^v; int64();
          v =   u; int64();
          w =   v; int64();
        }
        double RandomNumberStream::RandomDouble()
        {
          return int64() * 5.42101086242752217E-20f;
        }
        double RandomNumberStream::RandomDouble(double a, double b)
        {
          return int64() * 5.42101086242752217E-20f * (b-a) + a;
        }
        unsigned long long RandomNumberStream::RandomInteger()
        {
          return int64();
        }
        unsigned long long RandomNumberStream::RandomInteger(int a, int b)
        {
          return a + int64() % (b-a);
        }
        unsigned long long RandomNumberStream::int64()
        {
          u  = u * 2862933555777941757LL + 7046029254386353087LL;
          v ^= v>>17; v ^= v<<31; v ^= v>>8;
          w  = 4294957665U*(w & 0xffffffff) + (w>>32);
          unsigned long long x = u^(u<<21); x ^= x>>35; x ^= x<<4;
          return (x+v)^w;
        }
        

        【讨论】:

          【解决方案7】:

          我认为您可以使用 rand 来生成随机数。但是,您可以为 srand 赋予相同的值,例如 99,这样您的数字将是随机的,但每次都是可预测的。

          int iSecret = 0;
          
          /* initialize random seed: */
          srand ( 99 );
          
          /* generate secret number: */
          iSecret = rand();
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-10-28
            • 2011-07-18
            • 1970-01-01
            • 2011-01-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多