diff --git a/source/MonoGame.Extended/Math/FastRandom.cs b/source/MonoGame.Extended/Math/FastRandom.cs index 472518b6..dd0184c0 100644 --- a/source/MonoGame.Extended/Math/FastRandom.cs +++ b/source/MonoGame.Extended/Math/FastRandom.cs @@ -1,127 +1,342 @@ using System; +using System.Runtime.CompilerServices; using Microsoft.Xna.Framework; namespace MonoGame.Extended { + /// - /// A random number generator that uses a fast algorithm to generate random values. - /// The speed comes at the price of true 'randomness' though, there are noticeable - /// patterns & it compares quite unfavourably to other algorithms in that respect. - /// It's a good choice in situations where speed is more desirable than a - /// good random distribution, and a poor choice when random distribution is important. + /// Represents a pseudo-random number generator using a linear congruential generator algorithm. /// + /// + /// + /// This implementation uses the same constants as Microsoft Visual C++ rand() function: + /// + /// a=214013 + /// c=2531011 + /// m=2^31 + /// + /// + /// It provides high performance and speed, but comes at the price of having lower statistical quality, or true + /// 'randomness' compared to modern algorithms. The algorithm is deterministic based on the initial seed + /// value, making it suitable for reproducible sequences. + /// + /// + /// Note: This pseudo-random number generator exhibits noticeable patterns and should not be used for + /// cryptographic purposes or when a high-quality random distribution is critical. Consider using + /// for better statistical properties. + /// + /// public class FastRandom { - private int _state; + private readonly IFastRandomImpl _impl; - public FastRandom() - : this(1) + /// + /// Provides a thread-safe instance that may be used concurrently from any thread. + /// + public static FastRandom Shared { get; } = new FastRandom(new ThreadSafeFastRandomImpl()); + + /// + /// Initializes a new instance of the class using the default seed value. + /// + public FastRandom() : this(1) { + } + /// + /// Initializes a new instance of the class using the specified seed value. + /// + /// A number used to calculate a starting value for the pseudo-random number sequence. public FastRandom(int seed) { - if (seed < 1) - throw new ArgumentOutOfRangeException(nameof(seed), "seed must be greater than zero"); + _impl = new LinearCongruentialGeneratorImpl(seed); + } - _state = seed; + private FastRandom(IFastRandomImpl impl) + { + ArgumentNullException.ThrowIfNull(impl); + _impl = impl; } /// - /// Gets the next random integer value. + /// Returns a non-negative random integer. /// - /// A random positive integer. + /// A 32-bit signed integer that is greater than or equal to 0 and less than 32768. public int Next() { - _state = 214013*_state + 2531011; - return (_state >> 16) & 0x7FFF; + return _impl.Next(); } /// - /// Gets the next random integer value which is greater than zero and less than or equal to - /// the specified maxmimum value. + /// Returns a non-negative random integer that is less than or equal to the specified maximum. /// - /// The maximum random integer value to return. - /// A random integer value between zero and the specified maximum value. + /// The inclusive upper bound of the random number to be generated. + /// + /// A 32-bit signed integer that is greater than or equal to 0 and less than or equal to . + /// public int Next(int max) { - return (int) (max*NextSingle() + 0.5f); + return _impl.Next(max); } /// - /// Gets the next random integer between the specified minimum and maximum values. + /// Returns a random integer that is within a specified range. /// - /// The inclusive minimum value. - /// The inclusive maximum value. + /// The inclusive lower bound of the random number returned. + /// The inclusive upper bound of the random number returned. + /// + /// A 32-bit signed integer that is greater than or equal to and less than or equal to + /// . + /// public int Next(int min, int max) { - return (int) ((max - min)*NextSingle() + 0.5f) + min; + return _impl.Next(min, max); } /// - /// Gets the next random integer between the specified range of values. + /// Returns a random integer that is within a specified range. /// - /// A range representing the inclusive minimum and maximum values. - /// A random integer between the specified minumum and maximum values. + /// + /// A range representing the inclusive lower and upper bound of the random number to return. + /// + /// + /// A 32-bit signed integer that is greater than or equal to the and less than or + /// equal to the value of . + /// public int Next(Range range) { - return Next(range.Min, range.Max); + return _impl.Next(range); } /// - /// Gets the next random single value. + /// Returns a random floating-point number that is greater than or equal to 0.0 and less than 1.0. /// - /// A random single value between 0 and 1. + /// + /// A single-precision floating point number that is greater than or equal to 0.0 and less than 1.0. + /// public float NextSingle() { - return Next()/(float) short.MaxValue; + return _impl.NextSingle(); } /// - /// Gets the next random single value which is greater than zero and less than or equal to - /// the specified maxmimum value. + /// Returns a random floating-point number that is greater than or equal to 0.0 and less than the + /// specified maximum. /// - /// The maximum random single value to return. - /// A random single value between zero and the specified maximum value. + /// The exclusive upper bond of the random number generated. + /// + /// A single precision floating-point number that is greater than or equal to 0.0 and less than + /// . + /// public float NextSingle(float max) { - return max*NextSingle(); + return _impl.NextSingle(max); } /// - /// Gets the next random single value between the specified minimum and maximum values. + /// Returns a random floating-point number that is within a specified range. /// - /// The inclusive minimum value. - /// The inclusive maximum value. - /// A random single value between the specified minimum and maximum values. + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. + /// + /// A single-precision floating point number that is greater than or equal to and + /// less than . + /// public float NextSingle(float min, float max) { - return (max - min)*NextSingle() + min; + return _impl.NextSingle(min, max); } /// - /// Gets the next random single value between the specified range of values. + /// Returns a random floating-point number that is within a specified range. /// - /// A range representing the inclusive minimum and maximum values. - /// A random single value between the specified minimum and maximum values. + /// + /// A range representing the inclusive lower and exclusive upper bound of the random number returned. + /// + /// + /// A single-precision floating point number that is greater than or equal to the + /// and less than the value of + /// public float NextSingle(Range range) { - return NextSingle(range.Min, range.Max); + return _impl.NextSingle(range); } /// - /// Gets the next random angle value. + /// Returns a random angle between -π and π. /// - /// A random angle value. + /// + /// A random angle value in radians. + /// public float NextAngle() { - return NextSingle(-MathHelper.Pi, MathHelper.Pi); + return _impl.NextAngle(); } + /// + /// Gets a random unit vector. + /// + /// When this method returns, contains a unit vector with a random direction. public void NextUnitVector(out Vector2 vector) { - var angle = NextAngle(); - vector = new Vector2((float) Math.Cos(angle), (float) Math.Sin(angle)); + _impl.NextUnitVector(out vector); } + + #region IFastRandomImplementation + private interface IFastRandomImpl + { + int Next(); + int Next(int max); + int Next(int min, int max); + int Next(Range range); + float NextSingle(); + float NextSingle(float max); + float NextSingle(float min, float max); + float NextSingle(Range range); + float NextAngle(); + void NextUnitVector(out Vector2 vector); + } + #endregion + + #region Linear Congruential Generator + private sealed class LinearCongruentialGeneratorImpl : IFastRandomImpl + { + private const int MULTIPLIER = 214013; + private const int INCREMENT = 2531011; + + private int _state; + + public LinearCongruentialGeneratorImpl() : this(1) + { + + } + + public LinearCongruentialGeneratorImpl(int seed) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(seed); + _state = seed; + } + + public int Next() + { + _state = MULTIPLIER * _state + INCREMENT; + return (_state >> 16) & 0x7FFF; + } + + public int Next(int max) + { + return (int)(max * NextSingle() + 0.5f); + } + + public int Next(int min, int max) + { + return (int)((max - min) * NextSingle() + 0.5f) + min; + } + + public int Next(Range range) + { + return Next(range.Min, range.Max); + } + + public float NextSingle() + { + return Next() / (float)short.MaxValue; + } + + public float NextSingle(float max) + { + return max * NextSingle(); + } + + public float NextSingle(float min, float max) + { + return (max - min) * NextSingle() + min; + } + + public float NextSingle(Range range) + { + return NextSingle(range.Min, range.Max); + } + + public float NextAngle() + { + return NextSingle(-MathHelper.Pi, MathHelper.Pi); + } + + public void NextUnitVector(out Vector2 vector) + { + float angle = NextAngle(); + vector.X = MathF.Cos(angle); + vector.Y = MathF.Sin(angle); + } + } + #endregion + + #region ThreadSafeImpl + private sealed class ThreadSafeFastRandomImpl : IFastRandomImpl + { + [ThreadStatic] + private static LinearCongruentialGeneratorImpl t_random; + + private static LinearCongruentialGeneratorImpl LocalRandom => t_random ?? Create(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static LinearCongruentialGeneratorImpl Create() + { + t_random = new LinearCongruentialGeneratorImpl(); + return t_random; + } + + public int Next() + { + return LocalRandom.Next(); + } + + public int Next(int max) + { + return LocalRandom.Next(max); + } + + public int Next(int min, int max) + { + return LocalRandom.Next(min, max); + } + + public int Next(Range range) + { + return LocalRandom.Next(range.Min, range.Max); + } + public float NextSingle() + { + return LocalRandom.NextSingle(); + } + + public float NextSingle(float max) + { + return LocalRandom.NextSingle(max); + } + + public float NextSingle(float min, float max) + { + return LocalRandom.NextSingle(min, max); + } + + public float NextSingle(Range range) + { + return LocalRandom.NextSingle(range.Min, range.Max); + } + + public float NextAngle() + { + return LocalRandom.NextAngle(); + } + + public void NextUnitVector(out Vector2 vector) + { + LocalRandom.NextUnitVector(out vector); + } + } + #endregion } -} \ No newline at end of file +}