views:

208

answers:

5

Hi,

I want to repeat a random number sequence generated by a legacy software using the VBMath.Rnd and VBMath.Randomize functions in VB .NET

Reading on the documentation for those functions on MSDN i found that you are supposed to "reset" the generator calling Rnd with a negative value if you want the same seed to give you the same result sequence each time.

But doing some tests... things didn't work as expected.

The legacy software does something like this at the start of the application on different executions:

float[] rNums = new float[4];

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

And my code does something like this:

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

The results for this test are:

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

The sequence that i want to reproduce in the second code any number of times is the sequence generated from the hard coded initial state of the generator. That means the sequence you would get if you run the first code alone.

I can not change the first code.

Any idea on why the VBMath.Rnd and VBMath.Randomize functions arent working as expected?

Did i miss something?


ANSWER

Thee problem is that since the legacy code doesn't call Rnd with a negative value, the generator doesn't clear its state and the call to Rnd gets chained to the previous value of the seed (in this case, the hard-coded value).

To solve the problem and be able to repeat the process all over again without all the problems that would imply "reproducing" the initial state, i cloned the generator code and patched it so i could reproduce the same situation every time depending on a parameter.

I know.. its ugly.. but it solves my problem (Btw i also know that there are some rounding errors and that the generated values are not exact.. they differ in like the last digit or something) but i don't need exact precision.

The rounding error probably comes from my choice of language for the cloning of the algorithm. If someone could help out on how to get the exact same result (match the rounding errors) that would be nice.

The patched code follows.

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);
    }
}
A: 

I would stay away from the VB centric functions, and just use the Random class with a fixed seed (I assume the seed, if set, is not time-sensitive).

leppie
I would if I could. ;) but as said earlier, its legacy code, there are other applications already using the legacy part of the generation.The origin of the seed is not important, both codes use the same seed, but.. have different results.
kripto_ash
+1  A: 

MSDN says:

To repeat sequences of random numbers, call Rnd with a negative argument immediately before using Randomize with a numeric argument. Using Randomize with the same value for Number does not repeat the previous sequence.

Only one of the code samples you show calls Rnd with a negative argument immediately before using Randomize with a numeric argument.

If code B has a call to Rnd(-1), it should generate the same sequence on all runs. If the sequence generated by a run of code B ( with Rnd(-1) ) repeated that generated by a run of code A ( without Rnd(-1) ), then the different runs of code A would have have to generate the same sequence. This contradicts the information in MSDN.

Pete Kirkham
>If the sequence generated by a run of code B ( with Rnd(-1) ) >repeated that generated by a run of code A ( without Rnd(-1) ), >then the different runs of code A would have have to generate the >same sequence. This contradicts the information in MSDN.Regarding your last statement. Running code A more than once (but not sequentiality on the same execution, which i think was what you were referring to) _does indeed_ return the same results since the state of the generator is the default hard coded state. Just put both codes on a console application, execute it twice and you will see.
kripto_ash
+1  A: 

The second set of code works as expected and will repeatedly give you the same set of 4 numbers. The first set does not, because it lacks the Rnd(-1) entry. As MSDN says :

Using Randomize with the same value for Number does not repeat the previous sequence

Running the first set 3 times in a row gives this :

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

Removing the Rnd(-1) entry from your second set of code gives the same results as the first set. The functions work as expected. Randomize seeds the sequence, but does not restart it - only Rnd(negative number) does that. Basically, the first set of code is starting the random number generation at a point in the sequence which you have no control over.

CodeByMoonlight
Running code A more than once (but not sequentiality on the same execution, which i think was what you were referring to) does indeed return the same results since the state of the generator is the default hard coded state.The sequence that i want to reproduce any number of times is the one generated from the initial hard coded state.
kripto_ash
I'm gonna edit the question and add this.
kripto_ash
If I put both sets of code inside button_click events, and click the button three times, the output window for the result of the first set gives the results I posted. For the same thing of the second set, I get the same set of 4 numbers 3 times, as you posted.
CodeByMoonlight
by execution i mean application context or execution context. like executing the application 3 times, not 3 calls to the sequence inside the same execution cus as you mentioned the numbers generation is chained [ N(p) depends on N(p-1) ]The unpolluted case just shows up after initialization of the CLR context when the generator is on the hard coded or initial state.Btw i solved the problem already. Used reflector and cloned the generator code with a small patch. If you want to see the code just say it. Thanks.
kripto_ash
Interesting, please show the code. Also, since you answered your own question you should give your question the check mark.
Jim
Done. Thanks for the reminder.
kripto_ash
A: 


ANSWER

The problem is that since the legacy code doesn't call Rnd with a negative value, the generator doesn't clear its state and the call to Rnd gets chained to the previous value of the seed (in this case, the hard-coded value).

To solve the problem and be able to repeat the process all over again without all the problems that would imply "reproducing" the initial state, i cloned the generator code and patched it so i could reproduce the same situation every time depending on a parameter.

I know.. its ugly.. but it solves my problem (Btw i also know that there are some rounding errors and that the generated values are not exact.. they differ in like the last digit or something) but i don't need exact precision.

The rounding error probably comes from my choice of language for the cloning of the algorithm. If someone could help out on how to get the exact same result (match the rounding errors) that would be nice.

The patched code follows.

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);
    }
}
kripto_ash