diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs index 59bf7dab77c83c8e7ac56abe7fe000a7eb13a41e..8b4e14ebbc204cbc5b611dbd9641f52a078494d0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs @@ -112,7 +112,7 @@ public override int Next(int maxValue) public override int Next(int minValue, int maxValue) { - ulong exclusiveRange = (ulong)(maxValue - minValue); + ulong exclusiveRange = (ulong)((long)maxValue - minValue); if (exclusiveRange > 1) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.cs b/src/libraries/System.Private.CoreLib/src/System/Random.cs index 64b9eb97c0971668fd7c8f660a2bb1d8d78462bb..21ea60e442abfc1aea014d281b7619f372a9c206 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Random.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Random.cs @@ -52,7 +52,12 @@ protected private Random(bool isThreadSafeRandom) /// Returns a non-negative random integer. /// A 32-bit signed integer that is greater than or equal to 0 and less than . - public virtual int Next() => _impl.Next(); + public virtual int Next() + { + int result = _impl.Next(); + AssertInRange(result, 0, int.MaxValue); + return result; + } /// Returns a non-negative random integer that is less than the specified maximum. /// The exclusive upper bound of the random number to be generated. must be greater than or equal to 0. @@ -68,7 +73,9 @@ public virtual int Next(int maxValue) ThrowMaxValueMustBeNonNegative(); } - return _impl.Next(maxValue); + int result = _impl.Next(maxValue); + AssertInRange(result, 0, maxValue); + return result; } /// Returns a random integer that is within a specified range. @@ -86,12 +93,19 @@ public virtual int Next(int minValue, int maxValue) ThrowMinMaxValueSwapped(); } - return _impl.Next(minValue, maxValue); + int result = _impl.Next(minValue, maxValue); + AssertInRange(result, minValue, maxValue); + return result; } /// Returns a non-negative random integer. /// A 64-bit signed integer that is greater than or equal to 0 and less than . - public virtual long NextInt64() => _impl.NextInt64(); + public virtual long NextInt64() + { + long result = _impl.NextInt64(); + AssertInRange(result, 0, long.MaxValue); + return result; + } /// Returns a non-negative random integer that is less than the specified maximum. /// The exclusive upper bound of the random number to be generated. must be greater than or equal to 0. @@ -107,7 +121,9 @@ public virtual long NextInt64(long maxValue) ThrowMaxValueMustBeNonNegative(); } - return _impl.NextInt64(maxValue); + long result = _impl.NextInt64(maxValue); + AssertInRange(result, 0, maxValue); + return result; } /// Returns a random integer that is within a specified range. @@ -125,16 +141,28 @@ public virtual long NextInt64(long minValue, long maxValue) ThrowMinMaxValueSwapped(); } - return _impl.NextInt64(minValue, maxValue); + long result = _impl.NextInt64(minValue, maxValue); + AssertInRange(result, minValue, maxValue); + return result; } /// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. /// A single-precision floating point number that is greater than or equal to 0.0, and less than 1.0. - public virtual float NextSingle() => _impl.NextSingle(); + public virtual float NextSingle() + { + float result = _impl.NextSingle(); + AssertInRange(result); + return result; + } /// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. /// A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0. - public virtual double NextDouble() => _impl.NextDouble(); + public virtual double NextDouble() + { + double result = _impl.NextDouble(); + AssertInRange(result); + return result; + } /// Fills the elements of a specified array of bytes with random numbers. /// The array to be filled with random numbers. @@ -155,7 +183,12 @@ public virtual void NextBytes(byte[] buffer) /// Returns a random floating-point number between 0.0 and 1.0. /// A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0. - protected virtual double Sample() => _impl.Sample(); + protected virtual double Sample() + { + double result = _impl.Sample(); + AssertInRange(result); + return result; + } private static void ThrowMaxValueMustBeNonNegative() => throw new ArgumentOutOfRangeException("maxValue", SR.Format(SR.ArgumentOutOfRange_NeedNonNegNum, "maxValue")); @@ -163,6 +196,23 @@ public virtual void NextBytes(byte[] buffer) private static void ThrowMinMaxValueSwapped() => throw new ArgumentOutOfRangeException("minValue", SR.Format(SR.Argument_MinMaxValue, "minValue", "maxValue")); + [Conditional("DEBUG")] + private static void AssertInRange(long result, long minInclusive, long maxExclusive) + { + if (maxExclusive > minInclusive) + { + Debug.Assert(result >= minInclusive && result < maxExclusive, $"Expected {minInclusive} <= {result} < {maxExclusive}"); + } + else + { + Debug.Assert(result == minInclusive, $"Expected {minInclusive} == {result}"); + } + } + + [Conditional("DEBUG")] + private static void AssertInRange(double result) => + Debug.Assert(result >= 0.0 && result < 1.0f, $"Expected 0.0 <= {result} < 1.0"); + /// Random implementation that delegates all calls to a ThreadStatic Impl instance. private sealed class ThreadSafeRandom : Random { @@ -189,7 +239,12 @@ private sealed class ThreadSafeRandom : Random [MethodImpl(MethodImplOptions.NoInlining)] private static XoshiroImpl Create() => t_random = new(); - public override int Next() => LocalRandom.Next(); + public override int Next() + { + int result = LocalRandom.Next(); + AssertInRange(result, 0, int.MaxValue); + return result; + } public override int Next(int maxValue) { @@ -198,7 +253,9 @@ public override int Next(int maxValue) ThrowMaxValueMustBeNonNegative(); } - return LocalRandom.Next(maxValue); + int result = LocalRandom.Next(maxValue); + AssertInRange(result, 0, maxValue); + return result; } public override int Next(int minValue, int maxValue) @@ -208,10 +265,17 @@ public override int Next(int minValue, int maxValue) ThrowMinMaxValueSwapped(); } - return LocalRandom.Next(minValue, maxValue); + int result = LocalRandom.Next(minValue, maxValue); + AssertInRange(result, minValue, maxValue); + return result; } - public override long NextInt64() => LocalRandom.NextInt64(); + public override long NextInt64() + { + long result = LocalRandom.NextInt64(); + AssertInRange(result, 0, long.MaxValue); + return result; + } public override long NextInt64(long maxValue) { @@ -220,7 +284,9 @@ public override long NextInt64(long maxValue) ThrowMaxValueMustBeNonNegative(); } - return LocalRandom.NextInt64(maxValue); + long result = LocalRandom.NextInt64(maxValue); + AssertInRange(result, 0, maxValue); + return result; } public override long NextInt64(long minValue, long maxValue) @@ -230,12 +296,24 @@ public override long NextInt64(long minValue, long maxValue) ThrowMinMaxValueSwapped(); } - return LocalRandom.NextInt64(minValue, maxValue); + long result = LocalRandom.NextInt64(minValue, maxValue); + AssertInRange(result, minValue, maxValue); + return result; } - public override float NextSingle() => LocalRandom.NextSingle(); + public override float NextSingle() + { + float result = LocalRandom.NextSingle(); + AssertInRange(result); + return result; + } - public override double NextDouble() => LocalRandom.NextDouble(); + public override double NextDouble() + { + double result = LocalRandom.NextDouble(); + AssertInRange(result); + return result; + } public override void NextBytes(byte[] buffer) { diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Random.cs b/src/libraries/System.Runtime.Extensions/tests/System/Random.cs index 8a6491aef05308d37605254a661b195810c908bf..1cc1ccc21ec97ec6c161d69b764548ca33010e8a 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Random.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Random.cs @@ -130,6 +130,31 @@ public void Next_IntInt_AllValuesWithinSmallRangeHit(bool derived, bool seeded) Assert.DoesNotContain(44, hs); } + public static IEnumerable Next_IntInt_Next_IntInt_AllValuesAreWithinRange_MemberData() => + from derived in new[] { false, true } + from seeded in new[] { false, true } + from (int min, int max) pair in new[] + { + (1, 2), + (-10, -3), + (0, int.MaxValue), + (-1, int.MaxValue), + (int.MinValue, 0), + (int.MinValue, int.MaxValue), + } + select new object[] { derived, seeded, pair.min, pair.max }; + + [Theory] + [MemberData(nameof(Next_IntInt_Next_IntInt_AllValuesAreWithinRange_MemberData))] + public void Next_IntInt_Next_IntInt_AllValuesAreWithinRange(bool derived, bool seeded, int min, int max) + { + Random r = Create(derived, seeded); + for (int i = 0; i < 100; i++) + { + Assert.InRange(r.Next(min, max), min, max - 1); + } + } + [Theory] [InlineData(false, false)] [InlineData(false, true)] @@ -178,6 +203,31 @@ public void Next_LongLong_AllValuesWithinSmallRangeHit(bool derived, bool seeded Assert.DoesNotContain(44L, hs); } + public static IEnumerable Next_LongLong_Next_IntInt_AllValuesAreWithinRange_MemberData() => + from derived in new[] { false, true } + from seeded in new[] { false, true } + from (long min, long max) pair in new[] + { + (1L, 2L), + (0L, long.MaxValue), + (2147483648, 2147483658), + (-1L, long.MaxValue), + (long.MinValue, 0L), + (long.MinValue, long.MaxValue), + } + select new object[] { derived, seeded, pair.min, pair.max }; + + [Theory] + [MemberData(nameof(Next_LongLong_Next_IntInt_AllValuesAreWithinRange_MemberData))] + public void Next_LongLong_Next_IntInt_AllValuesAreWithinRange(bool derived, bool seeded, long min, long max) + { + Random r = Create(derived, seeded); + for (int i = 0; i < 100; i++) + { + Assert.InRange(r.NextInt64(min, max), min, max - 1); + } + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -502,7 +552,7 @@ public void Xoshiro_AlgorithmBehavesAsExpected() Assert.Equal(12, randOuter.Next(0, 42)); Assert.Equal(7234, randOuter.Next(42, 12345)); Assert.Equal(2147483642, randOuter.Next(int.MaxValue - 5, int.MaxValue)); - Assert.Equal(1981894504, randOuter.Next(int.MinValue, int.MaxValue)); + Assert.Equal(-1236260882, randOuter.Next(int.MinValue, int.MaxValue)); Assert.Equal(3644728249650840822, randOuter.NextInt64()); Assert.Equal(2809750975933744783, randOuter.NextInt64());