System.Random是否总是在不同平台上针对相同种子生成可预测的数字?

10

我知道Object.GetHashCode会根据平台的不同返回相同对象(例如相同的字符串)的不同值。因此,我不能依赖它来进行跨平台的简单哈希。

但是,我能否依赖System.Random?无论我是否在Microsoft .NET / Mono / x86 / x64等上,它是否会产生相同的种子结果?


1
我建议不要依赖一个叫做“Random”的类来生成可预测的数字,这可能是一个错误 :) - DavidG
2
@DavidG 在编程中,我们经常使用随机数生成器来生成可预测的结果,给定特定的种子值,以便进行确定性的问题复现等操作。 - vargonian
@LeiYang 我非常熟悉你所提到的问题,但那不是我遇到的问题。我需要在任何平台上都能保证相同的哈希值。 - vargonian
@vargonian 这可能很普遍,但这不是一个好主意。 - DavidG
2
@DavidG 在这种情况下,您建议如何以确定性的方式复制涉及许多 RNG 计算的游戏玩法场景等? - vargonian
显示剩余2条评论
2个回答

12

正如Jeremy在他的答案中提到的那样, 文档说明数字生成器不能保证跨.NET版本一致。

然而,文档还告诉你如何实现你自己的算法

您可以通过继承Random类并提供自己的随机数生成算法来实现自己的随机数生成器。要提供自己的算法,您必须重写Sample方法,该方法实现随机数生成算法。您还应该重写Next()、Next(Int32, Int32)和NextBytes方法,以确保它们调用您重写的Sample方法。您不必重写Next(Int32)和NextDouble方法。

使用这个方法,我们可以制作自己的随机类,使用已知的固定算法。例如,我们可以查看.Net源代码并实现当前的随机算法,这将允许我们使用ConsistantRandom类,并确保算法不会改变。
下面的示例源于.NET Core 1.1.0源代码
using System;

namespace ConsoleApplication1
{
    public class ConsistantRandom: Random
    {
        private const int MBIG = Int32.MaxValue;
        private const int MSEED = 161803398;
        private const int MZ = 0;

        private int inext;
        private int inextp;
        private int[] SeedArray = new int[56];

        public ConsistantRandom()
            : this(Environment.TickCount)
        {
        }

        public ConsistantRandom(int seed)
        {
            int ii;
            int mj, mk;

            int subtraction = (seed == Int32.MinValue) ? Int32.MaxValue : Math.Abs(seed);
            mj = MSEED - subtraction;
            SeedArray[55] = mj;
            mk = 1;
            for (int i = 1; i < 55; i++)
            {
                ii = (21 * i) % 55;
                SeedArray[ii] = mk;
                mk = mj - mk;
                if (mk < 0) mk += MBIG;
                mj = SeedArray[ii];
            }
            for (int k = 1; k < 5; k++)
            {
                for (int i = 1; i < 56; i++)
                {
                    SeedArray[i] -= SeedArray[1 + (i + 30) % 55];
                    if (SeedArray[i] < 0) SeedArray[i] += MBIG;
                }
            }
            inext = 0;
            inextp = 21;
        }
        protected override double Sample()
        {
            return (InternalSample() * (1.0 / MBIG));
        }

        private int InternalSample()
        {
            int retVal;
            int locINext = inext;
            int locINextp = inextp;

            if (++locINext >= 56) locINext = 1;
            if (++locINextp >= 56) locINextp = 1;

            retVal = SeedArray[locINext] - SeedArray[locINextp];

            if (retVal == MBIG) retVal--;
            if (retVal < 0) retVal += MBIG;

            SeedArray[locINext] = retVal;

            inext = locINext;
            inextp = locINextp;

            return retVal;
        }

        public override int Next()
        {
            return InternalSample();
        }

        private double GetSampleForLargeRange()
        {
            int result = InternalSample();
            bool negative = (InternalSample() % 2 == 0) ? true : false;
            if (negative)
            {
                result = -result;
            }
            double d = result;
            d += (Int32.MaxValue - 1);
            d /= 2 * (uint)Int32.MaxValue - 1;
            return d;
        }


        public override int Next(int minValue, int maxValue)
        {
            if (minValue > maxValue)
            {
                throw new ArgumentOutOfRangeException("minValue");
            }

            long range = (long)maxValue - minValue;
            if (range <= (long)Int32.MaxValue)
            {
                return ((int)(Sample() * range) + minValue);
            }
            else
            {
                return (int)((long)(GetSampleForLargeRange() * range) + minValue);
            }
        }
        public override void NextBytes(byte[] buffer)
        {
            if (buffer == null) throw new ArgumentNullException("buffer");
            for (int i = 0; i < buffer.Length; i++)
            {
                buffer[i] = (byte)(InternalSample() % (Byte.MaxValue + 1));
            }
        }
    }
    
}

由于 ConsistantRandom 派生自 Random,因此您可以将该类作为替换任何以前使用类型 Random 的地方的“插入式”替代品,只需将 Random rnd = new Random(yourSeed); 替换为 Random rnd = new ConsistantRandom(yourSeed);

7

不,你不能依赖在不同平台或版本中生成一致的值。根据.NET Framework文档中的System.Random(较少详细的.NET Core文档没有涉及此问题):

Random类中随机数生成器的实现不能保证在.NET Framework的主要版本之间保持相同。因此,在不同版本的.NET Framework中,您不应该假设相同的种子将导致相同的伪随机序列。


谢谢,这正是我要找的,我感到很惭愧没有在文档中发现这个。现在我必须寻找一些保证可预测性的东西,或者自己编写(因为它不需要太复杂)。 - vargonian
@vargonian:我在这里编写了一个简单的伪随机数生成器,如果你愿意,可以使用它或改进它:https://gist.github.com/caesay/5f79b958c025db084dcdfb50f69ce039 - caesay
1
@vargonian FYI [微软当前内置实现的源代码可在 MIT 许可证下获取](https://github.com/dotnet/coreclr/blob/release/1.1.0/src/mscorlib/src/System/Random.cs/),因此您可以使用重命名后的副本,从而确保安全和一致性。 - Jeremy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接