Изменение реализации на .NET's Random () - PullRequest
16 голосов
/ 18 марта 2012

Я мигрирую метод, который используется для декодирования из .NET Framework 1.1 в .NET Framework 4. Я заметил, что реализация Random изменилась.Таким образом, с учетом того же начального значения Random.NextBytes возвращает другой результат.

Поэтому, если я запускаю следующий код.

byte[] bytes = new byte[4];
System.Random random = new System.Random(50);
random.NextBytes(bytes);

for(int i=0; i< bytes.Length; i++)
{
  Console.WriteLine("bytes[" + i + "] = " + bytes[i]);
}

В .NET Framework 1.1 он возвращает:

bytes[0] = 216
bytes[1] = 124
bytes[2] = 183
bytes[3] =  58

В .NET Framework 4 возвращается:

bytes[0] = 154
bytes[1] =  49
bytes[2] = 183
bytes[3] =  48

Как лучше всего решить эту проблему?

Ответы [ 5 ]

27 голосов
/ 18 марта 2012

Это не проблема с Random, он отлично удовлетворяет задокументированному интерфейсу. Это проблема с вашим программным обеспечением, полагающимся на детали реализации. Учитесь на этой ошибке и не делайте этого снова .

Что касается решения проблемы, вы можете реализовать собственную версию генерации псевдослучайных чисел 1.1 для декодирования, а затем реализовать новый алгоритм кодирования / декодирования, который не зависит от нестабильного поведения (например, реализацию * 1006). * или GetHashCode) для вашей новой версии программного обеспечения.

17 голосов
/ 18 марта 2012

Вы можете просто использовать Reflector, чтобы скопировать класс Random из 1.1 mscorlib.

public class Random1_1
{
    // Fields
    private int inext;
    private int inextp;
    private const int MBIG = 0x7fffffff;
    private const int MSEED = 0x9a4ec86;
    private const int MZ = 0x0;
    private int[] SeedArray;

    // Methods
    public Random1_1()
        : this(Environment.TickCount)
    {
    }

    public Random1_1(int Seed)
    {
        this.SeedArray = new int[0x38];
        int num2 = 0x9a4ec86 - Math.Abs(Seed);
        this.SeedArray[0x37] = num2;
        int num3 = 0x1;
        for (int i = 0x1; i < 0x37; i++)
        {
            int index = (0x15 * i) % 0x37;
            this.SeedArray[index] = num3;
            num3 = num2 - num3;
            if (num3 < 0x0)
            {
                num3 += 0x7fffffff;
            }
            num2 = this.SeedArray[index];
        }
        for (int j = 0x1; j < 0x5; j++)
        {
            for (int k = 0x1; k < 0x38; k++)
            {
                this.SeedArray[k] -= this.SeedArray[0x1 + ((k + 0x1e) % 0x37)];
                if (this.SeedArray[k] < 0x0)
                {
                    this.SeedArray[k] += 0x7fffffff;
                }
            }
        }
        this.inext = 0x0;
        this.inextp = 0x15;
        Seed = 0x1;
    }

    public virtual int Next()
    {
        return (int)(this.Sample() * 2147483647.0);
    }

    public virtual int Next(int maxValue)
    {
        if (maxValue < 0x0)
        {
            throw new ArgumentOutOfRangeException("maxValue");
        }
        return (int)(this.Sample() * maxValue);
    }

    public virtual int Next(int minValue, int maxValue)
    {
        if (minValue > maxValue)
        {
            throw new ArgumentOutOfRangeException("minValue");
        }
        int num = maxValue - minValue;
        if (num < 0x0)
        {
            long num2 = maxValue - minValue;
            return (((int)((long)(this.Sample() * num2))) + minValue);
        }
        return (((int)(this.Sample() * num)) + minValue);
    }

    public virtual void NextBytes(byte[] buffer)
    {
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }
        for (int i = 0x0; i < buffer.Length; i++)
        {
            buffer[i] = (byte)(this.Sample() * 256.0);
        }
    }

    public virtual double NextDouble()
    {
        return this.Sample();
    }

    protected virtual double Sample()
    {
        int inext = this.inext;
        int inextp = this.inextp;
        if (++inext >= 0x38)
        {
            inext = 0x1;
        }
        if (++inextp >= 0x38)
        {
            inextp = 0x1;
        }
        int num = this.SeedArray[inext] - this.SeedArray[inextp];
        if (num < 0x0)
        {
            num += 0x7fffffff;
        }
        this.SeedArray[inext] = num;
        this.inext = inext;
        this.inextp = inextp;
        return (num * 4.6566128752457969E-10);
    }
}

Протестировано и дает желаемый результат.

3 голосов
/ 04 июля 2012

Здесь нет ответа, но в отличие от многих здесь присутствующих, я не думаю, что документирования смешного поведения достаточно, чтобы оправдать его.

Потому что зачем вам в первую очередь использовать механизм высева? Хорошо, я скажу вам: чтобы вы всегда могли воспроизвести случайную последовательность из одного семени, а не сохранять, возможно, миллионы случайных чисел. Обратите внимание, что я сказал «всегда», а не «до обновления до следующей версии .NET». Будучи несовместимыми между версиями, текущие генераторы случайных чисел .NET не обеспечивают эту функциональность. Microsoft должна была лучше выполнить это (или вообще не реализовывать) вместо того, чтобы просто документировать дефектное поведение.

И, кстати, хотя алгоритм действительно является деталью реализации, как же можно вызвать результат вызова метода как деталь реализации? Должен ли я действительно проверять документацию по каждому методу в .NET Framework, чтобы убедиться, что в следующей версии я не рискну получить другой результат от объединения двух строк или вычисления квадратного корня?

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

3 голосов
/ 18 марта 2012

Если вы абсолютно уверены в версии Random для .NET 1.1, то единственное, о чем я могу подумать, - это создать новую сборку, нацеленную на 1.1, и вызывать ее из обновленного приложения .NET 4.

Однако не могли бы вы рассказать, почему так важно поддерживать это семя?Там может быть лучший способ.

0 голосов
/ 19 марта 2012

В качестве альтернативы, можно ли предложить использовать класс System.Security.Cryptography.RandomNumberGenerator для генерации криптографически сильных случайных байтовых массивов?

RandomNumberGenerator rng = RandomNumberGenerator.Create();
byte[] bytes = new byte[128];
rng.GetBytes(bytes);

Я присоединюсь к остальным комментариям и упомяну, что полагаться на недокументированную реализацию плохо. Более того, если вы действительно полагаетесь на предсказуемую «случайность» - если вы используете это для чего-то, что должно быть «безопасным» - это совершенно неправильно.

...