隐藏VBMath随机数生成器行为

时间:2009-10-09 10:00:25

标签: c# .net vb.net math random

我想使用VB .NET中的VBMath.Rnd和VBMath.Randomize函数重复遗留软件生成的随机数序列

阅读MSDN上关于这些函数的文档,我发现如果你想让相同的种子每次给你相同的结果序列,你应该“重置”调用Rnd的生成器的值为负值。

但是做一些测试......事情并没有按预期发挥作用。

遗留软件在不同执行的应用程序开始时执行类似的操作:

float[] rNums = new float[4];

VBMath.Randomize(154341.77394338892);
for (int index = 0; index < 4; index++)
{
    rNums[index] = VBMath.Rnd();
}

我的代码做了类似的事情:

VBMath.Rnd(-1);
VBMath.Randomize(154341.77394338892);
for (int index = 0; index < 4; index++)
{
    Console.WriteLine("rNum[" + index + "] " + rNums[index] + " = " + VBMath.Rnd());
}

此测试的结果是:

rNum[0] 0,6918146 = 0,2605162
rNum[1] 0,5121228 = 0,4748411
rNum[2] 0,8309224 = 0,8112976
rNum[3] 0,972851  = 0,8011347

我想在第二个代码中多次重现的序列是从生成器的硬编码初始状态生成的序列。这意味着如果单独运行第一个代码,您将获得的序列。

我无法更改第一个代码。

有关为什么VBMath.Rnd和VBMath.Randomize函数没有按预期工作的任何想法?

我错过了什么吗?


ANSWER

问题在于,由于遗留代码不会使用负值调用Rnd,因此生成器不会清除其状态,并且对Rnd的调用会链接到种子的先前值(在这种情况下,很难编码值)。

为了解决这个问题并且能够重复这个过程而没有任何意味着“再现”初始状态的问题,我克隆了生成器代码并对其进行修补,这样我每次都能重现相同的情况,具体取决于一个参数。

我知道..它很丑..但它解决了我的问题(顺便说一下我也知道有一些舍入错误,并且生成的值不准确......它们与最后一位数字或其他东西不同)但我不知道不需要精确的精确度。

舍入误差可能来自我选择的克隆算法的语言。如果有人可以帮忙解决如何获得完全相同的结果(匹配舍入错误),那将是不错的。

修补后的代码如下。

public sealed class RndGenerator
{
    static int m_rndSeed = 0x50000;
    // This is the value that the programmer sets the seed at ProjectData object
    // initialization
    const int CONSTANT_INIT_RNDSEED = 0x50000; 

    // Methods
    private static float GetTimer()
    {
        DateTime now = DateTime.Now;
        return (float)(((((60 * now.Hour) + now.Minute) * 60) + now.Second) + (((double)now.Millisecond) / 1000.0));
    }

    public static void Randomize()
    {
        float timer = GetTimer();
        int rndSeed = m_rndSeed;
        int num = BitConverter.ToInt32(BitConverter.GetBytes(timer), 0);
        num = ((num & 0xffff) ^ (num >> 0x10)) << 8;
        rndSeed = (rndSeed & -16776961) | num;
        m_rndSeed = rndSeed;
    }

    public static void Randomize(double Number)
    {
        Randomize(Number, false);
    }

    public static void Randomize(double Number, bool useHardCodedState)
    {
        int num;

        int rndSeed = 0;
        if (useHardCodedState)
            rndSeed = CONSTANT_INIT_RNDSEED;
        else
            rndSeed = m_rndSeed;

        if (BitConverter.IsLittleEndian)
        {
            num = BitConverter.ToInt32(BitConverter.GetBytes(Number), 4);
        }
        else
        {
            num = BitConverter.ToInt32(BitConverter.GetBytes(Number), 0);
        }
        num = ((num & 0xffff) ^ (num >> 0x10)) << 8;
        rndSeed = (rndSeed & -16776961) | num;
        m_rndSeed = rndSeed;
    }

    public static float Rnd()
    {
        return Rnd(1f);
    }

    public static float Rnd(float Number)
    {
        int rndSeed = m_rndSeed;
        if (Number != 0.0)
        {
            if (Number < 0.0)
            {
                long num3 = BitConverter.ToInt32(BitConverter.GetBytes(Number), 0);
                num3 &= (long)0xffffffffL;
                rndSeed = (int)((num3 + (num3 >> 0x18)) & 0xffffffL);
            }
            rndSeed = (int)(((rndSeed * 0x43fd43fdL) + 0xc39ec3L) & 0xffffffL);
        }
        m_rndSeed = rndSeed;
        return (((float)rndSeed) / 1.677722E+07f);
    }
}

6 个答案:

答案 0 :(得分:1)

MSDN says

  

要重复随机数序列,请在使用带有数字参数的Randomize之前立即使用否定参数调用Rnd。使用具有相同Number值的Randomize不会重复前一个序列。

在使用带有数字参数的Randomize之前,您显示的代码示例中只有一个在使用负参数时调用Rnd。

如果代码B调用了Rnd(-1),它应该在所有运行中生成相同的序列。如果由一串代码B(带有Rnd(-1))生成的序列重复由代码A运行(没有Rnd(-1))生成的序列,则代码A的不同运行必须生成相同的代码。序列。这与MSDN中的信息相矛盾。

答案 1 :(得分:1)

第二组代码按预期工作,并将重复为您提供相同的4个数字。 第一组没有,因为它没有Rnd(-1)条目。正如MSDN所说:

  

对Number使用具有相同值的Randomize不会重复前一个序列

连续3次运行第一组给出:

rNum[0] 0 = 0.6918146
rNum[1] 0 = 0.5121228
rNum[2] 0 = 0.8309224
rNum[3] 0 = 0.972851
rNum[0] 0 = 0.5982737
rNum[1] 0 = 0.323263
rNum[2] 0 = 0.05594879
rNum[3] 0 = 0.5724301
rNum[0] 0 = 0.5555484
rNum[1] 0 = 0.8296129
rNum[2] 0 = 0.6523779
rNum[3] 0 = 0.6867073

从第二组代码中删除Rnd(-1)条目会得到与第一组相同的结果。这些功能按预期工作。随机化种子序列,但不重新启动它 - 只有Rnd(负数)才能这样做。 基本上,第一组代码是在您无法控制的序列中的某一点开始生成随机数。

答案 2 :(得分:1)

很久以前,你已经回答了你的主要问题,我在聚会上有点迟了。这是我的第一篇帖子,所以我没有声明在你的二次问题上添加关于舍入错误的评论,但是这里有:

我最近曾打电话使用Reflector来反编译VBMath的实现。我想制作一个非静态版本,这样我就可以同时使用多线程安全的VB6兼容Rnd()序列。

我遇到了和你一样的准确度错误。我花了一段时间,但我发现Reflector(我认为)搞砸了其中一个常数:

Rnd(float Number)函数中,更改:

return (((float)rndSeed) / 1.677722E+07f);

return (((float)rndSeed) / 16777216f);

How Visual Basic Generates Pseudo-Random Numbers for the RND Function 有正确的常数。

答案 3 :(得分:0)


ANSWER

问题在于,由于遗留代码不会使用负值调用Rnd,因此生成器不会清除其状态,并且对Rnd的调用会链接到种子的先前值(在这种情况下,很难编码值)。

为了解决这个问题并且能够重复这个过程而没有任何意味着“再现”初始状态的问题,我克隆了生成器代码并对其进行修补,这样我每次都能重现相同的情况,具体取决于一个参数。

我知道..它很丑..但它解决了我的问题(顺便说一下我也知道有一些舍入错误,并且生成的值不准确......它们与最后一位数字或其他东西不同)但我不知道不需要精确的精确度。

舍入误差可能来自我选择的克隆算法的语言。如果有人可以帮忙解决如何获得完全相同的结果(匹配舍入错误),那将是不错的。

修补后的代码如下。

public sealed class RndGenerator
{
    static int m_rndSeed = 0x50000;
    // This is the value that the programmer sets the seed at ProjectData object
    // initialization
    const int CONSTANT_INIT_RNDSEED = 0x50000; 

    // Methods
    private static float GetTimer()
    {
        DateTime now = DateTime.Now;
        return (float)(((((60 * now.Hour) + now.Minute) * 60) + now.Second) + (((double)now.Millisecond) / 1000.0));
    }

    public static void Randomize()
    {
        float timer = GetTimer();
        int rndSeed = m_rndSeed;
        int num = BitConverter.ToInt32(BitConverter.GetBytes(timer), 0);
        num = ((num & 0xffff) ^ (num >> 0x10)) << 8;
        rndSeed = (rndSeed & -16776961) | num;
        m_rndSeed = rndSeed;
    }

    public static void Randomize(double Number)
    {
        Randomize(Number, false);
    }

    public static void Randomize(double Number, bool useHardCodedState)
    {
        int num;

        int rndSeed = 0;
        if (useHardCodedState)
            rndSeed = CONSTANT_INIT_RNDSEED;
        else
            rndSeed = m_rndSeed;

        if (BitConverter.IsLittleEndian)
        {
            num = BitConverter.ToInt32(BitConverter.GetBytes(Number), 4);
        }
        else
        {
            num = BitConverter.ToInt32(BitConverter.GetBytes(Number), 0);
        }
        num = ((num & 0xffff) ^ (num >> 0x10)) << 8;
        rndSeed = (rndSeed & -16776961) | num;
        m_rndSeed = rndSeed;
    }

    public static float Rnd()
    {
        return Rnd(1f);
    }

    public static float Rnd(float Number)
    {
        int rndSeed = m_rndSeed;
        if (Number != 0.0)
        {
            if (Number < 0.0)
            {
                long num3 = BitConverter.ToInt32(BitConverter.GetBytes(Number), 0);
                num3 &= (long)0xffffffffL;
                rndSeed = (int)((num3 + (num3 >> 0x18)) & 0xffffffL);
            }
            rndSeed = (int)(((rndSeed * 0x43fd43fdL) + 0xc39ec3L) & 0xffffffL);
        }
        m_rndSeed = rndSeed;
        return (((float)rndSeed) / 1.677722E+07f);
    }
}

答案 4 :(得分:0)

只是在这里想要的是我的Java版本

public class Rnd{

private int Xi;
private static int m = (int) Math.pow(2, 24);
private static int a = 0x43fd43fd;
private static int c = 0xc39ec3;
private static int m_rndSeed = 0x50000;

public static float Rnd() {
    return Rnd(1f);
}

public static float Rnd(float number) {
    int rndSeed = m_rndSeed;
    if (number != 0.0) {
        if (number < 0.0) {
                long num3 = Float.floatToRawIntBits(number)& 0xffffffffL;
                rndSeed = (int) ((num3 + (num3 >> 0x18)) & 0xffffffL);
        }
        rndSeed = (int) (((rndSeed * a) + c) & 0xffffffL);
    }
    m_rndSeed = rndSeed;
    return (((float) rndSeed) / m);
}
}

使用private static int m =(int)Math.pow(2,24);而不是1.677722E + 07f修复了我的舍入问题。

答案 5 :(得分:-1)

我会远离VB中心函数,只使用带有固定种子的Random类(我假设种子,如果设置,则不是时间敏感的)。