C# 中的快速随机生成器
本文关键字:随机 | 更新日期: 2023-09-27 18:32:12
我需要随机数来表示游戏中的程序地形等。游戏基于种子。我被告知不要依赖 .NET 框架随机类,因为实现可能会更改,并且种子可能会导致其他版本中的其他值。(这已经发生了。NET 1.1 有不同的实现)
最随机的替代方案是密码学,其中的重点依赖于非常好的数字而不是性能。
所以我寻找的是一个非常快速的基于种子的伪随机数生成器,它独立于 .NET 版本,具有平均质量随机性。
不要重新发明轮子。借用/窃取有效的东西:
- 随机类的反编译 .NET implemmentation
- 随机的单声道源
- 随机的Java源代码(风险略高,因为它需要移植到.NET,因此一些代码更改尚未在野外测试一百万次。
只需使用现有的反编译实现之一,并将其折叠到您自己的库中,以确保它不会更改:
using System;
using System.Runtime;
using System.Runtime.InteropServices;
namespace System
{
/// <summary>Represents a pseudo-random number generator, a device that produces a sequence of numbers that meet certain statistical requirements for randomness.</summary>
/// <filterpriority>1</filterpriority>
[ComVisible(true)]
[Serializable]
public class Random
{
private const int MBIG = 2147483647;
private const int MSEED = 161803398;
private const int MZ = 0;
private int inext;
private int inextp;
private int[] SeedArray = new int[56];
/// <summary>Initializes a new instance of the <see cref="T:System.Random" /> class, using a time-dependent default seed value.</summary>
public Random() : this(Environment.TickCount)
{
}
/// <summary>Initializes a new instance of the <see cref="T:System.Random" /> class, using the specified seed value.</summary>
/// <param name="Seed">A number used to calculate a starting value for the pseudo-random number sequence. If a negative number is specified, the absolute value of the number is used. </param>
public Random(int Seed)
{
int num = (Seed == -2147483648) ? 2147483647 : Math.Abs(Seed);
int num2 = 161803398 - num;
this.SeedArray[55] = num2;
int num3 = 1;
for (int i = 1; i < 55; i++)
{
int num4 = 21 * i % 55;
this.SeedArray[num4] = num3;
num3 = num2 - num3;
if (num3 < 0)
{
num3 += 2147483647;
}
num2 = this.SeedArray[num4];
}
for (int j = 1; j < 5; j++)
{
for (int k = 1; k < 56; k++)
{
this.SeedArray[k] -= this.SeedArray[1 + (k + 30) % 55];
if (this.SeedArray[k] < 0)
{
this.SeedArray[k] += 2147483647;
}
}
}
this.inext = 0;
this.inextp = 21;
Seed = 1;
}
/// <summary>Returns a random number between 0.0 and 1.0.</summary>
/// <returns>A double-precision floating point number greater than or equal to 0.0, and less than 1.0.</returns>
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
protected virtual double Sample()
{
return (double)this.InternalSample() * 4.6566128752457969E-10;
}
private int InternalSample()
{
int num = this.inext;
int num2 = this.inextp;
if (++num >= 56)
{
num = 1;
}
if (++num2 >= 56)
{
num2 = 1;
}
int num3 = this.SeedArray[num] - this.SeedArray[num2];
if (num3 == 2147483647)
{
num3--;
}
if (num3 < 0)
{
num3 += 2147483647;
}
this.SeedArray[num] = num3;
this.inext = num;
this.inextp = num2;
return num3;
}
/// <summary>Returns a nonnegative random number.</summary>
/// <returns>A 32-bit signed integer greater than or equal to zero and less than <see cref="F:System.Int32.MaxValue" />.</>/returns>
/// <filterpriority>1</filterpriority>
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public virtual int Next()
{
return this.InternalSample();
}
private double GetSampleForLargeRange()
{
int num = this.InternalSample();
bool flag = this.InternalSample() % 2 == 0;
if (flag)
{
num = -num;
}
double num2 = (double)num;
num2 += 2147483646.0;
return num2 / 4294967293.0;
}
/// <summary>Returns a random number within a specified range.</summary>
/// <returns>A 32-bit signed integer greater than or equal to <paramref name="minValue" /> and less than <paramref name="maxValue" />; that is, the range of return values includes <paramref name="minValue" /> but not <paramref name="maxValue" />. If <paramref name="minValue" /> equals <paramref name="maxValue" />, <paramref name="minValue" /> is returned.</returns>
/// <param name="minValue">The inclusive lower bound of the random number returned. </param>
/// <param name="maxValue">The exclusive upper bound of the random number returned. <paramref name="maxValue" /> must be greater than or equal to <paramref name="minValue" />. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="minValue" /> is greater than <paramref name="maxValue" />. </exception>
/// <filterpriority>1</filterpriority>
public virtual int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException("minValue", Environment.GetResourceString("Argument_MinMaxValue", new object[]
{
"minValue",
"maxValue"
}));
}
long num = (long)maxValue - (long)minValue;
if (num <= 2147483647L)
{
return (int)(this.Sample() * (double)num) + minValue;
}
return (int)((long)(this.GetSampleForLargeRange() * (double)num) + (long)minValue);
}
/// <summary>Returns a nonnegative random number less than the specified maximum.</summary>
/// <returns>A 32-bit signed integer greater than or equal to zero, and less than <paramref name="maxValue" />; that is, the range of return values ordinarily includes zero but not <paramref name="maxValue" />. However, if <paramref name="maxValue" /> equals zero, <paramref name="maxValue" /> is returned.</returns>
/// <param name="maxValue">The exclusive upper bound of the random number to be generated. <paramref name="maxValue" /> must be greater than or equal to zero. </param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="maxValue" /> is less than zero. </exception>
/// <filterpriority>1</filterpriority>
public virtual int Next(int maxValue)
{
if (maxValue < 0)
{
throw new ArgumentOutOfRangeException("maxValue", Environment.GetResourceString("ArgumentOutOfRange_MustBePositive", new object[]
{
"maxValue"
}));
}
return (int)(this.Sample() * (double)maxValue);
}
/// <summary>Returns a random number between 0.0 and 1.0.</summary>
/// <returns>A double-precision floating point number greater than or equal to 0.0, and less than 1.0.</returns>
/// <filterpriority>1</filterpriority>
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public virtual double NextDouble()
{
return this.Sample();
}
/// <summary>Fills the elements of a specified array of bytes with random numbers.</summary>
/// <param name="buffer">An array of bytes to contain random numbers. </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer" /> is null. </exception>
/// <filterpriority>1</filterpriority>
public virtual void NextBytes(byte[] buffer)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)(this.InternalSample() % 256);
}
}
}
}
标准Random
(或您自己的副本)可能适用于地形和许多东西,但您需要小心某些游戏,您使用产生足够随机性的生成器。
拿一包牌来说,如果使用 32 位生成器,它只能产生 40 亿个第一包(见下文)排列 (2^32)。然而,实际上有52个!(阶乘即)可能,即 8x10^67。这需要一个 226 位的数字。
因此,有时可能需要使用加密生成器。
为什么这仅适用于第一包?
我假设下一个数字的种子只是最后返回的 32 位值。我现在查看了代码,内部种子的位数要高得多。您为其提供的 32 位外部种子值仅用作为较大种子生成起点的一种方式。它维护和修改这个内部种子,同时产生随机值。这一切都意味着,是的,只有40亿个序列,但这些序列是独一无二的,它们不会像我假设的那样重叠。这意味着最初只能有 40 亿包,但随后可能会有更多。