Непонятное поведение генератора случайных чисел VBMath - PullRequest
1 голос
/ 09 октября 2009

Я хочу повторить последовательность случайных чисел, сгенерированную унаследованным программным обеспечением с использованием функций 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 ]

1 голос
/ 07 ноября 2010

Вы давно ответили на свой главный вопрос, и я немного опоздал на вечеринку. Это мой первый пост, поэтому у меня нет репутации, чтобы добавить комментарий к вашему второстепенному вопросу об ошибках округления, но здесь говорится:

Недавно мне позвонили, чтобы использовать Reflector для декомпиляции реализации VBMath. Я хотел создать нестатическую версию, чтобы в одно и то же время можно было использовать несколько потоковобезопасных последовательностей Rnd (), совместимых с VB6.

Я попал в ту же погрешность, что и ты. Это заняло у меня некоторое время, но я обнаружил, что Reflector (я думаю) спутал одну из констант:

В функции Rnd(float Number) измените:

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

до

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

Как Visual Basic генерирует псевдослучайные числа для функции RND имеет правильную постоянную.

1 голос
/ 09 октября 2009

Второй набор кода работает, как и ожидалось, и будет неоднократно выдавать один и тот же набор из 4 цифр. Первый набор - нет, поскольку в нем отсутствует запись Rnd (-1). Как MSDN говорит:

Использование Randomize с тем же значением для Number не повторяет предыдущую последовательность

Запуск первого сета 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 (отрицательное число). По сути, первый набор кода запускает генерацию случайных чисел в той точке последовательности, которую вы не можете контролировать.

1 голос
/ 09 октября 2009

MSDN говорит :

Чтобы повторить последовательности случайных чисел, вызовите Rnd с отрицательным аргументом непосредственно перед использованием Randomize с числовым аргументом. Использование Randomize с тем же значением для Number не повторяет предыдущую последовательность.

Только один из примеров кода, который вы показываете, вызывает Rnd с отрицательным аргументом непосредственно перед использованием Randomize с числовым аргументом.

Если код B имеет вызов Rnd (-1), он должен генерировать одну и ту же последовательность при всех запусках. Если последовательность, сгенерированная прогоном кода B (с Rnd (-1)), повторяет последовательность, сгенерированную прогоном кода A (без Rnd (-1)), то различные прогоны кода A должны будут генерировать одинаковые последовательность. Это противоречит информации в MSDN.

0 голосов
/ 18 апреля 2013

Просто если кто-то захочет, вот моя версия 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);
}
}

Использование приватной статической функции int m = (int) Math.pow (2, 24); вместо 1.677722E + 07f исправлена ​​ошибка округления.

0 голосов
/ 13 октября 2009

ОТВЕТ

Проблема в том, что, поскольку унаследованный код не вызывает 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);
    }
}
0 голосов
/ 09 октября 2009

Я бы держался подальше от центрических функций VB и просто использовал класс Random с фиксированным начальным числом (я предполагаю, что начальное число, если установлено, не чувствительно ко времени).

...