代码之家  ›  专栏  ›  技术社区  ›  kripto_ash

模糊的vbmath随机数生成器行为

  •  0
  • kripto_ash  · 技术社区  · 15 年前

    我想重复使用vbmath.rnd和vbmath.randomize函数在vb.net中由旧软件生成的随机数序列。

    阅读文档以了解 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函数不能按预期工作吗?

    我错过什么了吗?


    回答

    问题是,由于遗留代码不使用负值调用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 回复  |  直到 11 年前
        1
  •  1
  •   Pete Kirkham    15 年前

    MSDN says 以下内容:

    要重复随机数序列,请在使用带数字参数的随机化之前立即使用负参数调用rnd。使用具有相同数字值的随机化不会重复前面的序列。

    在使用带有数字参数的随机化之前,仅显示带有负参数的调用rnd的代码示例之一。

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

        2
  •  1
  •   MartW    15 年前

    第二组代码按预期工作,并将重复地为您提供同一组4个数字。 第一组没有,因为它缺少RND(-1)条目。AS MSDN 说:

    对数字使用相同值的随机化不会重复前面的序列

    连续运行第一组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(负数)这样做。 基本上,第一组代码是在您无法控制的序列中的某个点开始随机数生成。

        3
  •  1
  •   Ben Creswick    14 年前

    你早就回答了你的主要问题,我参加晚会有点晚了。这是我的第一篇文章,因此我没有声誉对您关于舍入错误的第二个问题添加评论,但这里是:

    我最近调用Reflector来反编译vbMath的实现。我想制作一个非静态版本,这样就可以同时运行多个线程安全vb6兼容的rnd()序列。

    我也犯了和你同样的错误。我花了一段时间,但我发现反射镜(我想)弄坏了其中一个常量:

    Rnd(float Number) 功能,更改:

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

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

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

        4
  •  0
  •   kripto_ash    15 年前

    回答

    问题是,由于遗留代码不使用负值调用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);
        }
    }
    
        5
  •  0
  •   bentech    11 年前

    这里的任何人都是我的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修复了我的舍入问题。

        6
  •  -1
  •   leppie    15 年前

    我将远离以VB为中心的函数,只使用 Random 用固定种子初始化(如果设置了种子,则假定该种子不区分时间)。